diff --git a/src/Webserver.API/Enums/ApiErrorCode.cs b/src/Webserver.API/Enums/ApiErrorCode.cs index 4764e60..b429af8 100644 --- a/src/Webserver.API/Enums/ApiErrorCode.cs +++ b/src/Webserver.API/Enums/ApiErrorCode.cs @@ -235,8 +235,5 @@ public enum ApiErrorCode /// Invalid Parameters provided (null/empty string that is forbidden?, invalid ticket length?, wrong datetime string format (rfc3339)? ...) /// InvalidParams = -32602 - - - } -} +} \ No newline at end of file diff --git a/src/Webserver.API/Enums/ApiPlcProgramBlockType.cs b/src/Webserver.API/Enums/ApiPlcProgramBlockType.cs new file mode 100644 index 0000000..5f5d59f --- /dev/null +++ b/src/Webserver.API/Enums/ApiPlcProgramBlockType.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2023, Siemens AG +// +// SPDX-License-Identifier: MIT + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Runtime.Serialization; + +namespace Siemens.Simatic.S7.Webserver.API.Enums +{ + /// + /// Block type enumeration. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum ApiPlcProgramBlockType + { + /// + /// OB block. + /// + [EnumMember(Value = "ob")] + Ob, + + /// + /// FB block. + /// + [EnumMember(Value = "fc")] + Fc, + + /// + /// FC block. + /// + [EnumMember(Value = "fb")] + Fb, + + /// + /// SFB block. + /// + [EnumMember(Value = "sfc")] + Sfc, + + /// + /// SFC block. + /// + [EnumMember(Value = "sfb")] + Sfb, + } +} diff --git a/src/Webserver.API/Models/ApiPlcProgramBrowseCodeBlocksData.cs b/src/Webserver.API/Models/ApiPlcProgramBrowseCodeBlocksData.cs index caf709a..33d5560 100644 --- a/src/Webserver.API/Models/ApiPlcProgramBrowseCodeBlocksData.cs +++ b/src/Webserver.API/Models/ApiPlcProgramBrowseCodeBlocksData.cs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Siemens.Simatic.S7.Webserver.API.Enums; using System; namespace Siemens.Simatic.S7.Webserver.API.Models @@ -18,7 +19,7 @@ public class ApiPlcProgramBrowseCodeBlocksData : IEquatable public ApiPlcProgramBrowseCodeBlocksData ShallowCopy() { - return (ApiPlcProgramBrowseCodeBlocksData) this.MemberwiseClone(); + return (ApiPlcProgramBrowseCodeBlocksData)this.MemberwiseClone(); } /// @@ -37,13 +38,13 @@ public ApiPlcProgramBrowseCodeBlocksData ShallowCopy() /// Type of the code block. /// [JsonProperty("block_type")] - public string BlockType { get; set; } + public ApiPlcProgramBlockType BlockType { get; set; } /// /// Constructor. /// [JsonConstructor] - public ApiPlcProgramBrowseCodeBlocksData(string name, ushort blockNumber, string blockType) + public ApiPlcProgramBrowseCodeBlocksData(string name, ushort blockNumber, ApiPlcProgramBlockType blockType) { Name = name; BlockNumber = blockNumber; diff --git a/tests/Webserver.API.UnitTests/ApiRequestTests.cs b/tests/Webserver.API.UnitTests/ApiRequestTests.cs index df5eb12..441fadc 100644 --- a/tests/Webserver.API.UnitTests/ApiRequestTests.cs +++ b/tests/Webserver.API.UnitTests/ApiRequestTests.cs @@ -8,7 +8,6 @@ using Siemens.Simatic.S7.Webserver.API.Enums; using Siemens.Simatic.S7.Webserver.API.Exceptions; using Siemens.Simatic.S7.Webserver.API.Models; -using Siemens.Simatic.S7.Webserver.API.Models.ApiDiagnosticBuffer; using Siemens.Simatic.S7.Webserver.API.Models.FailsafeParameters; using Siemens.Simatic.S7.Webserver.API.Models.Requests; using Siemens.Simatic.S7.Webserver.API.Models.Responses; @@ -1075,6 +1074,69 @@ public void T013_06_ApiPlcProgramBrowse_InvalidArrayIndex_ExcThrown() Assert.ThrowsAsync(async () => await TestHandler.PlcProgramBrowseAsync(ApiPlcProgramBrowseMode.Children, "\"DataTypes\".\"Bool\"a")); } + /// + /// + /// + /// + [Test] + public async Task T014_01_PlcProgramDownloadProfilingData_Success() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramDownloadProfilingDataSuccess); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + ApiSingleStringResponse response = await TestHandler.PlcProgramDownloadProfilingDataAsync(); + + Assert.That(response.Result.Equals("jgxikeMgLryvP0YoHc.eqt8BY787")); + } + + /// + /// + /// + /// + [Test] + public void T014_02_PlcProgramDownloadProfilingData_NoResources() + { + // This case could happen if the user downloads the profiling + // data twice without clearing the ticket or downloading the actual data. + + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramDownloadProfilingDataNoResources); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + Assert.ThrowsAsync(async () => await TestHandler.PlcProgramDownloadProfilingDataAsync()); + } + + /// + /// + /// + /// + [Test] + public void T014_03_PlcProgramDownloadProfilingData_PermissionDenied() + { + + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramDownloadProfilingDataPermissionDenied); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + Assert.ThrowsAsync(async () => await TestHandler.PlcProgramDownloadProfilingDataAsync()); + } + /// /// /// @@ -1093,6 +1155,148 @@ public void T015_ApiPlcProgramBrowse_PermissionDenied_ExcThrown() Assert.ThrowsAsync(async () => await TestHandler.PlcProgramBrowseAsync(ApiPlcProgramBrowseMode.Children, "\"DataTypes\".\"Bool\"")); } + /// + /// + /// + /// + [Test] + public async Task T015_02_ApiPlcProgramBrowseCodeBlocks_EmptyResult() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramBrowseCodeBlocksEmptyResult); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + ApiPlcProgramBrowseCodeBlocksResponse response = await TestHandler.PlcProgramBrowseCodeBlocksAsync(); + Assert.That(response.Result.Count == 0); + } + + /// + /// + /// + /// + [Test] + public void T015_03_ApiPlcProgramBrowseCodeBlocks_InvalidParams() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramBrowseCodeBlocksInvalidParams); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + Assert.ThrowsAsync(async () => await TestHandler.PlcProgramBrowseCodeBlocksAsync()); + } + + /// + /// + /// + /// + [Test] + public async Task T015_04_ApiPlcProgramBrowseCodeBlocks_Success() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramBrowseCodeBlocksSuccess); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + ApiPlcProgramBrowseCodeBlocksResponse response = await TestHandler.PlcProgramBrowseCodeBlocksAsync(); + + Assert.That(response.Result.Count == 5); + + Assert.That(response.Result[0].Name == "Main"); + Assert.That(response.Result[0].BlockType == ApiPlcProgramBlockType.Ob); + Assert.That(response.Result[0].BlockNumber == 1); + + Assert.That(response.Result[1].Name == "USEND"); + Assert.That(response.Result[1].BlockType == ApiPlcProgramBlockType.Sfb); + Assert.That(response.Result[1].BlockNumber == 8); + + Assert.That(response.Result[2].Name == "COPY_HW"); + Assert.That(response.Result[2].BlockType == ApiPlcProgramBlockType.Sfc); + Assert.That(response.Result[2].BlockNumber == 65509); + + Assert.That(response.Result[3].Name == "PRODTEST"); + Assert.That(response.Result[3].BlockType == ApiPlcProgramBlockType.Fb); + Assert.That(response.Result[3].BlockNumber == 65522); + + Assert.That(response.Result[4].Name == "FC_14325"); + Assert.That(response.Result[4].BlockType == ApiPlcProgramBlockType.Fc); + Assert.That(response.Result[4].BlockNumber == 14325); + } + + /// + /// + /// + /// + [Test] + public async Task T015_05_ApiPlcProgramBrowseCodeBlocks_EmptyBlockName() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramBrowseCodeBlocksEmptyBlockName); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + ApiPlcProgramBrowseCodeBlocksResponse response = await TestHandler.PlcProgramBrowseCodeBlocksAsync(); + + Assert.That(response.Result.Count == 1); + Assert.That(response.Result[0].Name == String.Empty); + Assert.That(response.Result[0].BlockType == ApiPlcProgramBlockType.Ob); + Assert.That(response.Result[0].BlockNumber == 1); + } + + /// + /// + /// + /// + [Test] + public void T015_06_ApiPlcProgramBrowseCodeBlocks_StringAsBlockNumber() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramBrowseCodeBlocksStringAsNumber); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + Assert.ThrowsAsync(async () => await TestHandler.PlcProgramBrowseCodeBlocksAsync()); + } + + /// + /// + /// + /// + [Test] + public void T015_07_ApiPlcProgramBrowseCodeBlocks_PermissionDenied() + { + var mockHttp = new MockHttpMessageHandler(); + // Setup a respond for the user api (including a wildcard in the URL) + mockHttp.When(HttpMethod.Post, $"https://{Ip.ToString()}/api/jsonrpc") + .Respond("application/json", ResponseStrings.PlcProgramBrowseCodeBlocksPermissionDenied); // Respond with JSON + // Inject the handler or client into your application code + var client = new HttpClient(mockHttp); + client.BaseAddress = new Uri($"https://{Ip.ToString()}"); + TestHandler = new ApiHttpClientRequestHandler(client, ApiRequestFactory, ApiResponseChecker); + + Assert.ThrowsAsync(async () => await TestHandler.PlcProgramBrowseCodeBlocksAsync()); + } + /// /// /// diff --git a/tests/Webserver.API.UnitTests/ResponseStrings.cs b/tests/Webserver.API.UnitTests/ResponseStrings.cs index 95963a4..e5e423c 100644 --- a/tests/Webserver.API.UnitTests/ResponseStrings.cs +++ b/tests/Webserver.API.UnitTests/ResponseStrings.cs @@ -48,10 +48,21 @@ public static class ResponseStrings public const string PlcProgramBrowseErrorStruct = "{\"jsonrpc\":\"2.0\",\"id\":\"ibf8wom\",\"result\":[{\"name\":\"ERROR_ID\",\"db_number\":1,\"datatype\":\"word\"},{\"name\":\"FLAGS\",\"db_number\":1,\"datatype\":\"byte\"},{\"name\":\"REACTION\",\"db_number\":1,\"datatype\":\"byte\"},{\"name\":\"CODE_ADDRESS\",\"has_children\":true,\"db_number\":1,\"datatype\":\"cref\"},{\"name\":\"MODE\",\"db_number\":1,\"datatype\":\"byte\"},{\"name\":\"OPERAND_NUMBER\",\"db_number\":1,\"datatype\":\"uint\"},{\"name\":\"POINTER_NUMBER_LOCATION\",\"db_number\":1,\"datatype\":\"uint\"},{\"name\":\"SLOT_NUMBER_SCOPE\",\"db_number\":1,\"datatype\":\"uint\"},{\"name\":\"DATA_ADDRESS\",\"has_children\":true,\"db_number\":1,\"datatype\":\"nref\"}]}"; public const string PlcProgramBrowseVarIsNotAStructure = "{\"jsonrpc\":\"2.0\",\"id\":\"5q27h2n\",\"error\":{\"code\":202,\"message\":\"Variable is not a structure\"}}"; + public const string PlcProgramBrowseCodeBlocksEmptyResult = "{\"jsonrpc\":\"2.0\",\"id\":\"y9vedkb9\",\"result\":[]}"; + public const string PlcProgramBrowseCodeBlocksInvalidParams = "{\"jsonrpc\":\"2.0\",\"id\":55,\"error\":{\"code\":-32602,\"message\":\"Invalid Params\"}}"; + public const string PlcProgramBrowseCodeBlocksSuccess = "{\"jsonrpc\":\"2.0\",\"id\":96,\"result\":[{\"name\":\"Main\",\"block_number\":1,\"block_type\":\"ob\"},{\"name\":\"USEND\",\"block_number\":8,\"block_type\":\"sfb\"},{\"name\":\"COPY_HW\",\"block_number\":65509,\"block_type\":\"sfc\"},{\"name\":\"PRODTEST\",\"block_number\":65522,\"block_type\":\"fb\"},{\"name\":\"FC_14325\",\"block_number\":14325,\"block_type\":\"fc\"}]}"; + public const string PlcProgramBrowseCodeBlocksEmptyBlockName = "{\"jsonrpc\":\"2.0\",\"id\":96,\"result\":[{\"name\":\"\",\"block_number\":1,\"block_type\":\"ob\"}]}"; + public const string PlcProgramBrowseCodeBlocksStringAsNumber = "{\"jsonrpc\":\"2.0\",\"id\":96,\"result\":[{\"name\":\"Main\",\"block_number\":\"abcdefg\",\"block_type\":\"ob\"},{\"name\":\"USEND\",\"block_number\":8,\"block_type\":\"sfb\"},{\"name\":\"COPY_HW\",\"block_number\":65509,\"block_type\":\"sfc\"},{\"name\":\"PRODTEST\",\"block_number\":65522,\"block_type\":\"sfc\"}]}"; + public const string PlcProgramBrowseCodeBlocksPermissionDenied = "{\"jsonrpc\":\"2.0\",\"id\":50,\"error\":{\"code\":2,\"message\":\"Permission denied\"}}"; + public const string PlcProgramInvalidAddress = "{\"jsonrpc\":\"2.0\",\"id\":\"5uxrl166\",\"error\":{\"code\":201,\"message\":\"Invalid address\"}}"; public const string PlcProgramAddressDoesNotExist = "{\"jsonrpc\":\"2.0\",\"id\":\"8buk8ryn\",\"error\":{\"code\":200,\"message\":\"Address does not exist\"}}"; public const string PlcProgramnInvalidArrayIndex = "{\"jsonrpc\":\"2.0\",\"id\":\"f5eqwla\",\"error\":{\"code\":203,\"message\":\"Invalid array index\"}}"; + public const string PlcProgramDownloadProfilingDataSuccess = "{\"jsonrpc\":\"2.0\",\"id\": 6,\"result\":\"jgxikeMgLryvP0YoHc.eqt8BY787\"}"; + public const string PlcProgramDownloadProfilingDataPermissionDenied = "{\"jsonrpc\":\"2.0\",\"id\": 18,\"error\":{\"code\": 2,\"message\":\"Permission denied\"}}"; + public const string PlcProgramDownloadProfilingDataNoResources = "{\"jsonrpc\":\"2.0\",\"id\":28,\"error\":{\"code\":4,\"message\":\"No Resources\"}}"; + public const string PlcProgramUnsupportedAddress = "{\"jsonrpc\":\"2.0\",\"id\":\"1wwe2z8j\",\"error\":{\"code\":204,\"message\":\"Unsupported address\"}}"; public const string PlcProgramReadFalseBool = "{\"jsonrpc\":\"2.0\",\"id\":\"aax109lc\",\"result\":false}";