Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c716300
feat(api): Modified output to UpdateItemsAsync
rlabbeptc Jul 8, 2025
700327f
refactor(test): modified test LoadTagGroupsRecursiveAsync_ShouldLoadT…
rlabbeptc Jul 8, 2025
6840514
Refactor(api): Updated method name to GetSupportedDriversAsync
rlabbeptc Jul 8, 2025
0e01169
feat(api): leverage cache driver list if present
rlabbeptc Jul 8, 2025
808fa56
chore(doc): Updated Comments
rlabbeptc Jul 8, 2025
bbbbb4a
feat(api): Added GetChannelAsync api and test
rlabbeptc Jul 9, 2025
4e1b6c3
Merge branch 'main' into assorted-cleanup
rlabbeptc Jul 9, 2025
b744691
feat(api): Updated intg testing for GetChannelAync
rlabbeptc Jul 9, 2025
789de7c
fix(test): updated test with result return change to UpdateItemsAsync
rlabbeptc Jul 9, 2025
85761ff
feat(api): Add GetDeviceAsync API and testing
rlabbeptc Jul 9, 2025
fe6248b
feat(api): #45 updated service handing and tests
rlabbeptc Jul 9, 2025
b65b066
feat(api): Updated ProductId handlers
rlabbeptc Jul 9, 2025
7da2cf1
chore(logging): Updated log messages for consistancy
rlabbeptc Jul 21, 2025
86a80c2
fix(api): RecursiveEndpoint missing suffix for TagGroups
rlabbeptc Jul 24, 2025
f7d1096
fix(api): Modified Endpoint for DeviceTagCollection class to be recur…
rlabbeptc Jul 24, 2025
5fc1c1f
refactor(api): Updated based on DeviceTagGroupTagCollection removal
rlabbeptc Jul 24, 2025
98faa58
chore(logging): updated log message
rlabbeptc Jul 24, 2025
3d6a400
chore(doc): Added documentation for clarity
rlabbeptc Jul 24, 2025
c94803b
chore: updated template
rlabbeptc Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ public async Task AutomaticTagGenerationAsync_ShouldReturnKepServerJobPromise_Wh

// Act
var result = await _kepwareApiClient.ApiServices.AutomaticTagGenerationAsync(UNIT_TEST_CHANNEL, UNIT_TEST_DEVICE, TimeSpan.FromSeconds(30));
var jobResult = await result.AwaitCompletionAsync();

// Assert
result.ShouldNotBeNull();
result.Endpoint.ShouldBe(ENDPOINT_TAG_GENERATION);
result.JobTimeToLive.ShouldBe(TimeSpan.FromSeconds(30));
jobResult.Value.ShouldBeFalse();
jobResult.IsSuccess.ShouldBeFalse();
jobResult.ResponseCode.ShouldBe(ApiResponseCode.BadRequest);
}

[Fact]
Expand Down Expand Up @@ -113,6 +117,7 @@ public async Task AutomaticTagGenerationAsync_ShouldReturnSuccess_WhenJobComplet
// Assert
completionResult.Value.ShouldBeTrue();
completionResult.IsSuccess.ShouldBeTrue();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.Success);
}

[Fact]
Expand All @@ -135,6 +140,7 @@ public async Task AutomaticTagGenerationAsync_ShouldReturnSuccess_WhenJobComplet
// Assert
completionResult.Value.ShouldBeTrue();
completionResult.IsSuccess.ShouldBeTrue();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.Success);
}

[Fact]
Expand All @@ -155,6 +161,7 @@ public async Task AutomaticTagGenerationAsync_ShouldReturnFailure_WhenJobFailsAf
// Assert
completionResult.Value.ShouldBeFalse();
completionResult.IsSuccess.ShouldBeFalse();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.Timeout);
}

[Fact]
Expand All @@ -180,6 +187,8 @@ public async Task AutomaticTagGenerationAsync_ShouldReturnFailure_WhenJobFailsAf
// Assert
completionResult.Value.ShouldBeFalse();
completionResult.IsSuccess.ShouldBeFalse();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.ServiceUnavailable);
completionResult.Message.ShouldBe(jobStatusFailed.Message);
}
}
}
12 changes: 8 additions & 4 deletions Kepware.Api.Test/ApiClient/GetProductInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ public async Task GetProductInfoAsync_ShouldReturnProductInfo_WhenApiRespondsSuc

#region GetProductInfoAsync - SupportsJsonProjectLoadService

//TODO: Add more test cases for TKS versions as well. Different product name
[Theory]
[InlineData("KEPServerEX", "12", 6, 17, true)] // Supports JSON Project Load Service (6.17+)
[InlineData("KEPServerEX", "12", 6, 16, false)] // Does not support it (6.16)
[InlineData("ThingWorxKepwareEdge", "13", 1, 10, true)] // Supports it (1.10+)
[InlineData("ThingworxKepwareServer", "12", 6, 17, true)] // Supports JSON Project Load Service (6.17+)
[InlineData("ThingworxKepwareServer", "12", 6, 16, false)] // Does not support it (6.16)
[InlineData("ThingWorxKepwareEdge", "13", 1, 10, true)] // Supports JSON Project Load Service (1.10+)
[InlineData("ThingWorxKepwareEdge", "13", 1, 9, false)] // Does not support it (1.9)
[InlineData("Kepware Edge", "13", 1, 0, true)] // Supports JSON Project Load Service
[InlineData("UnknownProduct", "99", 10, 0, false)] // Unknown product, should be false
public async Task GetProductInfoAsync_ShouldReturnCorrect_SupportsJsonProjectLoadService(
string productName, string productId, int majorVersion, int minorVersion, bool expectedResult)
Expand All @@ -62,8 +64,10 @@ public async Task GetProductInfoAsync_ShouldReturnCorrect_SupportsJsonProjectLoa
#region GetProductInfoAsync - ProductType

[Theory]
[InlineData("KEPServerEX", "12", ProductType.KEPServerEX)]
[InlineData("ThingWorxKepwareEdge", "13", ProductType.ThingWorxKepwareEdge)]
[InlineData("KEPServerEX", "12", ProductType.KepwareServer)]
[InlineData("ThingworxKepwareServer", "12", ProductType.KepwareServer)]
[InlineData("ThingWorxKepwareEdge", "13", ProductType.KepwareEdge)]
[InlineData("Kepware Edge", "13", ProductType.KepwareEdge)]
[InlineData("UnknownProduct", "99", ProductType.Unknown)]
[InlineData("InvalidProduct", "abc", ProductType.Unknown)] // Invalid ID, should be Unknown
public async Task GetProductInfoAsync_ShouldReturnCorrect_ProductType(
Expand Down
6 changes: 3 additions & 3 deletions Kepware.Api.Test/ApiClient/LoadEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ public async Task LoadEntityAsync_ShouldReturnTagCollection_WhenApiRespondsSucce
.ReturnsResponse(tagsJson, "application/json");

// Act
var result = await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagGroupTagCollection, Tag>(new DeviceTagGroup("B Registers", new Device("16 Bit Device", "Data Type Examples")));
var result = await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagCollection, Tag>(new DeviceTagGroup("B Registers", new Device("16 Bit Device", "Data Type Examples")));

// Assert
Assert.NotNull(result);
Expand All @@ -363,15 +363,15 @@ public async Task LoadEntityAsync_ShouldReturnTagCollection_WhenApiRespondsSucce

#endregion

#region LoadEntityAsync - Exception Fall
#region LoadEntityAsync - Exception Fail

[Fact]
public async Task LoadEntityAsync_ShouldThrowInvalidOperationException_WhenLoadRecursiveEndpointWithStringList()
{
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagGroupTagCollection, Tag>(["Data Type Examples", "16 Bit Device", "B Registers"]);
await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagCollection, Tag>(["Data Type Examples", "16 Bit Device", "B Registers"]);
});

Assert.Equal("Recursive endpoint does not support string list item name", exception.Message);
Expand Down
99 changes: 98 additions & 1 deletion Kepware.Api.Test/ApiClient/ProjectApiHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,49 @@ public async Task GetOrCreateChannelAsync_ShouldCreateChannel_WhenChannelDoesNot
Assert.Equal(channelName, result.Name);
}

[Fact]
public async Task GetChannelAsync_ShouldReturnChannel_WhenChannelExists()
{
// Arrange
var channelName = "ExistingChannel";
var channelJson = """
{
"PROJECT_ID": 676550906,
"common.ALLTYPES_NAME": "ExistingChannel",
"common.ALLTYPES_DESCRIPTION": "Example Channel",
"servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Simulator"
}
""";

_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channelName}")
.ReturnsResponse(channelJson, "application/json");

// Act
var result = await _projectApiHandler.Channels.GetChannelAsync(channelName);

// Assert
Assert.NotNull(result);
Assert.Equal(channelName, result.Name);
}

[Fact]
public async Task GetChannelAsync_ShouldReturnNull_WhenChannelDoesNotExist()
{
// Arrange
await ConfigureToServeDrivers();

var channelName = "NewChannel";

_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channelName}")
.ReturnsResponse(HttpStatusCode.NotFound);

// Act
var result = await _projectApiHandler.Channels.GetChannelAsync(channelName);

// Assert
Assert.Null(result);
}

[Fact]
public async Task UpdateChannelAsync_ShouldReturnTrue_WhenUpdateIsSuccessful()
{
Expand Down Expand Up @@ -190,6 +233,57 @@ public async Task GetOrCreateDeviceAsync_ShouldCreateDevice_WhenDeviceDoesNotExi
Assert.Equal(deviceName, result.Name);
}

[Fact]
public async Task GetDeviceAsync_ShouldReturnDevice_WhenDeviceExists()
{
// Arrange
await ConfigureToServeDrivers();
var channel = new Channel { Name = "ExistingChannel" };
var deviceName = "ExistingDevice";
var deviceJson = """
{
"PROJECT_ID": 676550906,
"common.ALLTYPES_NAME": "ExistingDevice",
"common.ALLTYPES_DESCRIPTION": "Example Device",
"servermain.DEVICE_CHANNEL_ASSIGNMENT": "ExistingChannel"
}
""";

_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}/devices/{deviceName}")
.ReturnsResponse(deviceJson, "application/json");

_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}/devices/{deviceName}/tags")
.ReturnsResponse("[]", "application/json");

_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}/devices/{deviceName}/tag_groups")
.ReturnsResponse("[]", "application/json");

// Act
var result = await _projectApiHandler.Devices.GetDeviceAsync(channel, deviceName);

// Assert
Assert.NotNull(result);
Assert.Equal(deviceName, result.Name);
}

[Fact]
public async Task GetDeviceAsync_ShouldReturnNull_WhenDeviceDoesNotExist()
{
// Arrange
await ConfigureToServeDrivers();
var channel = new Channel { Name = "ExistingChannel", DeviceDriver = "Simulator" };
var deviceName = "NewDevice";

_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}/devices/{deviceName}")
.ReturnsResponse(HttpStatusCode.NotFound);

// Act
var result = await _projectApiHandler.Devices.GetDeviceAsync(channel, deviceName);

// Assert
Assert.Null(result);
}

[Fact]
public async Task UpdateDeviceAsync_ShouldReturnTrue_WhenUpdateIsSuccessful()
{
Expand Down Expand Up @@ -269,7 +363,8 @@ public async Task LoadTagGroupsRecursiveAsync_ShouldLoadTagGroupsCorrectly()
.ReturnsResponse("[]", "application/json");

var tagGroup = new DeviceTagGroup { Name = "TagGroup1", Owner = device };
var tagGroups = new List<DeviceTagGroup> { tagGroup };
var tagGroup2 = new DeviceTagGroup { Name = "TagGroup1", Owner = tagGroup };
var tagGroups = new List<DeviceTagGroup> { tagGroup , tagGroup2 };

// Act
await ProjectApiHandler.LoadTagGroupsRecursiveAsync(_kepwareApiClient, tagGroups);
Expand All @@ -278,6 +373,8 @@ public async Task LoadTagGroupsRecursiveAsync_ShouldLoadTagGroupsCorrectly()
Assert.NotNull(tagGroup.TagGroups);
Assert.Single(tagGroup.TagGroups);
Assert.Equal("TagGroup1", tagGroup.TagGroups.First().Name);
Assert.NotNull(tagGroup2.TagGroups);
Assert.Empty(tagGroup2.TagGroups);
}

#endregion
Expand Down
2 changes: 1 addition & 1 deletion Kepware.Api.Test/ApiClient/ProjectLoadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ private static void CompareTagGroupsRecursive(DeviceTagGroupCollection? expected
var thisName = parentName + "/" + ExpectedTagGroup.Name;
if (ExpectedTagGroup.Tags?.Count > 0 || ActualTagGroup.Tags?.Count > 0)
{
var tagCompareResult = EntityCompare.Compare<DeviceTagGroupTagCollection, Tag>(ExpectedTagGroup.Tags, ExpectedTagGroup.Tags);
var tagCompareResult = EntityCompare.Compare<DeviceTagCollection, Tag>(ExpectedTagGroup.Tags, ExpectedTagGroup.Tags);
tagCompareResult.ShouldNotBeNull();
tagCompareResult.UnchangedItems.ShouldNotBeEmpty($"All tags in device {thisName} should be unchanged.");
tagCompareResult.ChangedItems.ShouldBeEmpty($"No tags in device {thisName} should be changed.");
Expand Down
13 changes: 12 additions & 1 deletion Kepware.Api.Test/ApiClient/ReinitializeRuntimeAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ public async Task ReinitializeRuntimeAsync_ShouldReturnKepServerJobPromise_WhenA

// Act
var result = await _kepwareApiClient.ApiServices.ReinitializeRuntimeAsync(TimeSpan.FromSeconds(30));

var jobResult = await result.AwaitCompletionAsync();

// Assert
result.ShouldNotBeNull();
result.Endpoint.ShouldBe("/config/v1/project/services/ReinitializeRuntime");
result.JobTimeToLive.ShouldBe(TimeSpan.FromSeconds(30));
jobResult.Value.ShouldBeFalse();
jobResult.IsSuccess.ShouldBeFalse();
jobResult.ResponseCode.ShouldBe(ApiResponseCode.BadRequest);

}

[Fact]
Expand Down Expand Up @@ -111,6 +116,7 @@ public async Task ReinitializeRuntimeAsync_ShouldReturnSuccess_WhenJobCompletesS
// Assert
completionResult.Value.ShouldBeTrue();
completionResult.IsSuccess.ShouldBeTrue();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.Success);
}

[Fact]
Expand All @@ -133,6 +139,7 @@ public async Task ReinitializeRuntimeAsync_ShouldReturnSuccess_WhenJobCompletesS
// Assert
completionResult.Value.ShouldBeTrue();
completionResult.IsSuccess.ShouldBeTrue();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.Success);
}

[Fact]
Expand All @@ -153,6 +160,8 @@ public async Task ReinitializeRuntimeAsync_ShouldReturnFailure_WhenJobFailsAfter
// Assert
completionResult.Value.ShouldBeFalse();
completionResult.IsSuccess.ShouldBeFalse();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.Timeout);

}

[Fact]
Expand All @@ -178,6 +187,8 @@ public async Task ReinitializeRuntimeAsync_ShouldReturnFailure_WhenJobFailsAfter
// Assert
completionResult.Value.ShouldBeFalse();
completionResult.IsSuccess.ShouldBeFalse();
completionResult.ResponseCode.ShouldBe(ApiResponseCode.ServiceUnavailable);
completionResult.Message.ShouldBe(jobStatusFailed.Message);
}
}
}
4 changes: 2 additions & 2 deletions Kepware.Api.Test/Util/EndpointResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void ResolveEndpoint_ShouldReturnCorrectRecursiveEndpoint()
var owner = new DeviceTagGroup("Values", new DeviceTagGroup("B Registers", new Device("16 Bit Device", new Channel("Data Type Examples"))));

// Act
var endpoint = EndpointResolver.ResolveEndpoint<DeviceTagGroupTagCollection>(owner);
var endpoint = EndpointResolver.ResolveEndpoint<DeviceTagCollection>(owner);

// Assert
Assert.Equal("/config/v1/project/channels/Data%20Type%20Examples/devices/16%20Bit%20Device/tag_groups/B%20Registers/tag_groups/Values/tags", endpoint);
Expand Down Expand Up @@ -98,7 +98,7 @@ public void ResolveEndpoint_ShouldThrowException_WhenRecursiveEndpointDoesNotSup

// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
EndpointResolver.ResolveEndpoint<DeviceTagGroupTagCollection>(owner, "Boolean1")
EndpointResolver.ResolveEndpoint<DeviceTagCollection>(owner, "Boolean1")
);

Assert.Equal("Recursive endpoint does not support item name", exception.Message);
Expand Down
4 changes: 2 additions & 2 deletions Kepware.Api.TestIntg/ApiClient/LoadEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public async Task LoadEntityAsync_ShouldReturnTagCollection_WhenApiRespondsSucce
var tagList = await AddSimulatorTestTags(device);

// Act
var result = await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagGroupTagCollection, Tag>(device);
var result = await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagCollection, Tag>(device);

// Assert
Assert.NotNull(result);
Expand Down Expand Up @@ -318,7 +318,7 @@ public async Task LoadEntityAsync_ShouldReturnTagCollectionFromTagGroup_WhenApiR
var tagList = await AddSimulatorTestTags(tagGroup);

// Act
var result = await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagGroupTagCollection, Tag>(tagGroup);
var result = await _kepwareApiClient.GenericConfig.LoadCollectionAsync<DeviceTagCollection, Tag>(tagGroup);

// Assert
Assert.NotNull(result);
Expand Down
Loading
Loading