diff --git a/README.md b/README.md index af0340a5..1232a8de 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ sequenceDiagram C-->>B: Submodel Template (Semantic IDs) B->>B: Extract semantic IDs B->>D: POST /data/{submodelId} - D-->D: Resolve semantic IDs + D-->>D: Resolve semantic IDs D-->>B: Semantic Id with Values B->>B: Populate Template B-->>A: Filled submodel @@ -68,7 +68,7 @@ The DataEngine transforms **static AAS templates** into **live digital represent When a client requests AAS data (shell descriptor, submodel, or submodel element): -1. **Fetch Template** - DataEngine retrieves the required AAS/Submodel template from the AAS Template Registry +1. **Fetch Template** - DataEngine retrieves the required AAS/Submodel template from the Template Registry/Template Repository 2. **Extract Semantic IDs** - Identifies all semantic IDs within the template that need values 3. **Request Data via Schema** - Sends a JSON Schema to the Plugin API describing the structure and semantic IDs needed 4. **Receive Values** - Plugin queries its database and responds with populated values for the requested semantic IDs diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProviderTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProviderTests.cs index dcbea15c..489fe68b 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProviderTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProviderTests.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; @@ -7,6 +7,8 @@ using AAS.TwinEngine.DataEngine.Infrastructure.Providers.SubmodelRegistryProvider.Services; using AAS.TwinEngine.DataEngine.UnitTests.Infrastructure.Providers.PluginDataProvider.Services; +using AasCore.Aas3_1; + using Microsoft.Extensions.Logging; using NSubstitute; @@ -44,6 +46,81 @@ public async Task GetDataForSubmodelDescriptorByIdAsync_ReturnsSubmodelDesciptor Assert.Equal(expectedDescriptor.Id, result.Id); } + [Fact] + public async Task GetDataForSubmodelDescriptorByIdAsync_DeserializesAasNestedTypes_WhenResponseContainsAasPayload() + { + const string id = "https://mm-software.com/submodel/nameplate"; + const string jsonResponse = """ + { + "description": [ + { + "language": "en", + "text": "Nameplate Submodel Template" + } + ], + "displayName": [ + { + "language": "en", + "text": "Nameplate" + } + ], + "extensions": [ + { + "name": "templateSource", + "valueType": "xs:string", + "value": "Nameplate" + } + ], + "administration": { + "version": "1", + "revision": "0" + }, + "idShort": "Nameplate", + "id": "https://mm-software.com/submodel/nameplate", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate" + } + ] + } + } + """; + + using var messageHandler = new FakeHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(jsonResponse) + })); + using var httpClient = new HttpClient(messageHandler); + httpClient.BaseAddress = new Uri("https://mm-software/fakeUrl"); + _clientFactory.CreateClient(HttpClientNames.SubmodelRegistry).Returns(httpClient); + + var result = await _sut.GetDataForSubmodelDescriptorByIdAsync(id, CancellationToken.None); + + Assert.NotNull(result.Description); + Assert.Equal("en", result.Description![0].Language); + Assert.Equal("Nameplate Submodel Template", result.Description[0].Text); + + Assert.NotNull(result.DisplayName); + Assert.Equal("Nameplate", result.DisplayName![0].Text); + + Assert.NotNull(result.Extensions); + Assert.Equal("templateSource", result.Extensions![0].Name); + Assert.Equal(DataTypeDefXsd.String, result.Extensions[0].ValueType); + Assert.Equal("Nameplate", result.Extensions[0].Value); + + Assert.NotNull(result.Administration); + Assert.Equal("1", result.Administration!.Version); + Assert.Equal("0", result.Administration.Revision); + + Assert.NotNull(result.SemanticId); + Assert.Equal(ReferenceTypes.ExternalReference, result.SemanticId!.Type); + Assert.Equal("https://admin-shell.io/zvei/nameplate/2/0/Nameplate", result.SemanticId.Keys[0].Value); + } + [Fact] public async Task GetDataForSubmodelDescriptorByIdAsync_ThrowsResponseParsingException_WhenDeserializationFails() { diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs index 6dd2918b..d027d78b 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/TemplateProvider/Services/TemplateProviderTests.cs @@ -122,6 +122,85 @@ public async Task GetShellDescriptorTemplateAsync_ReturnsShellDescriptor_WhenVal Assert.Equal("https://admin-shell.io/idta/asset/ContactInformation/1/0", result.GlobalAssetId); } + [Fact] + public async Task GetShellDescriptorTemplateAsync_DeserializesAasNestedTypes_WhenResponseContainsAasPayload() + { + const string JsonResponse = """ + { + "description": [ + { + "language": "en", + "text": "Template Asset Administration Shell for example environments." + } + ], + "displayName": [ + { + "language": "en", + "text": "AAS Template" + } + ], + "extensions": [ + { + "name": "templateSource", + "valueType": "xs:string", + "value": "ShellTemplate" + } + ], + "administration": { + "version": "1", + "revision": "0" + }, + "assetKind": "Instance", + "id": "https://mm-software.com/aas/aasTemplate", + "specificAssetIds": [ + { + "name": "SerialNumber", + "value": "SN-9429", + "externalSubjectId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://example.com/subjects/serial-number" + } + ] + } + } + ] + } + """; + + using var mockHttpResponse = new HttpResponseMessage(HttpStatusCode.OK); + mockHttpResponse.Content = new StringContent(JsonResponse); + using var mockHttpMessageHandler = new FakeHttpMessageHandler(mockHttpResponse); + using var httpClient = new HttpClient(mockHttpMessageHandler); + httpClient.BaseAddress = new Uri("https://www.mm-software.com/fakeurl"); + _httpClientFactory.CreateClient(HttpClientNames.AasRegistry).Returns(httpClient); + + var result = await _sut.GetShellDescriptorTemplateAsync(TemplateId, CancellationToken.None); + + Assert.NotNull(result.Description); + Assert.Equal("en", result.Description![0].Language); + Assert.Equal("Template Asset Administration Shell for example environments.", result.Description[0].Text); + + Assert.NotNull(result.DisplayName); + Assert.Equal("AAS Template", result.DisplayName![0].Text); + + Assert.NotNull(result.Extensions); + Assert.Equal("templateSource", result.Extensions![0].Name); + Assert.Equal(DataTypeDefXsd.String, result.Extensions[0].ValueType); + Assert.Equal("ShellTemplate", result.Extensions[0].Value); + + Assert.NotNull(result.Administration); + Assert.Equal("1", result.Administration!.Version); + Assert.Equal("0", result.Administration.Revision); + + Assert.NotNull(result.SpecificAssetIds); + Assert.Equal("SerialNumber", result.SpecificAssetIds![0].Name); + Assert.NotNull(result.SpecificAssetIds[0].ExternalSubjectId); + Assert.Equal("https://example.com/subjects/serial-number", result.SpecificAssetIds[0].ExternalSubjectId!.Keys[0].Value); + } + [Fact] public async Task GetShellDescriptorTemplateAsync_ThrowsResponseParsingException_WhenInvalidJsonResponse() { diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Shared/AasJsonNodeDeserializerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Shared/AasJsonNodeDeserializerTests.cs new file mode 100644 index 00000000..e13a97e0 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Shared/AasJsonNodeDeserializerTests.cs @@ -0,0 +1,156 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +using AAS.TwinEngine.DataEngine.Infrastructure.Shared; + +using AasCore.Aas3_1; + +namespace AAS.TwinEngine.DataEngine.UnitTests.Infrastructure.Shared; + +public class AasJsonNodeDeserializerTests +{ + [Fact] + public void UnwrapResult_ReturnsResultNode_WhenResultPropertyExists() + { + var root = JsonNode.Parse(""" + { + "result": { + "id": "shell-1" + } + } + """); + + var result = AasJsonNodeDeserializer.UnwrapResult(root); + + Assert.NotNull(result); + Assert.Equal("shell-1", result!["id"]!.GetValue()); + } + + [Fact] + public void UnwrapResult_ReturnsRootNode_WhenResultPropertyDoesNotExist() + { + var root = JsonNode.Parse(""" + { + "id": "shell-2" + } + """); + + var result = AasJsonNodeDeserializer.UnwrapResult(root); + + Assert.Same(root, result); + } + + [Fact] + public void DeserializeAasNode_ReturnsNull_WhenNodeIsNull() + { + var result = AasJsonNodeDeserializer.DeserializeAasNode(null, _ => "value"); + + Assert.Null(result); + } + + [Fact] + public void DeserializeAasNode_ReturnsDeserializedValue_WhenNodeIsProvided() + { + var node = JsonNode.Parse("""{ "value": "abc" }"""); + + var result = AasJsonNodeDeserializer.DeserializeAasNode(node, n => n["value"]?.GetValue()); + + Assert.Equal("abc", result); + } + + [Fact] + public void DeserializeAasArray_ReturnsNull_WhenNodeIsNotArray() + { + var node = JsonNode.Parse("""{ "name": "not-an-array" }"""); + + var result = AasJsonNodeDeserializer.DeserializeAasArray(node, n => n["name"]?.GetValue()); + + Assert.Null(result); + } + + [Fact] + public void DeserializeAasArray_ReturnsFilteredArray_WhenArrayContainsNullEntries() + { + var node = JsonNode.Parse(""" + [ + { "name": "one" }, + null, + { "name": "two" } + ] + """); + + var result = AasJsonNodeDeserializer.DeserializeAasArray(node, n => n["name"]?.GetValue()); + + Assert.NotNull(result); + Assert.Equal(2, result!.Count); + Assert.Equal("one", result[0]); + Assert.Equal("two", result[1]); + } + + [Fact] + public void DeserializeObject_ReturnsDefault_WhenNodeIsNull() + { + var result = AasJsonNodeDeserializer.DeserializeObject(null); + + Assert.Null(result); + } + + [Fact] + public void DeserializeObject_UsesDefaultOptions_WhenOptionsAreNotProvided() + { + var node = JsonNode.Parse("""{ "name": "Default" }"""); + + var result = AasJsonNodeDeserializer.DeserializeObject(node); + + Assert.NotNull(result); + Assert.Equal("Default", result!.Name); + } + + [Fact] + public void DeserializeObject_UsesProvidedOptions_WhenOptionsAreProvided() + { + var node = JsonNode.Parse("""{ "NAME": "CaseInsensitive" }"""); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + var result = AasJsonNodeDeserializer.DeserializeObject(node, options); + + Assert.NotNull(result); + Assert.Equal("CaseInsensitive", result!.Name); + } + + [Fact] + public void DeserializeEnum_ReturnsNull_WhenNodeIsNull() + { + var result = AasJsonNodeDeserializer.DeserializeEnum(null); + + Assert.Null(result); + } + + [Fact] + public void DeserializeEnum_ReturnsEnumValue_WhenNodeContainsValidValue() + { + var node = JsonNode.Parse("\"Instance\""); + + var result = AasJsonNodeDeserializer.DeserializeEnum(node); + + Assert.Equal(AssetKind.Instance, result); + } + + [Fact] + public void DeserializeEnum_ReturnsNull_WhenNodeContainsInvalidValue() + { + var node = JsonNode.Parse("\"NotARealEnumValue\""); + + var result = AasJsonNodeDeserializer.DeserializeEnum(node); + + Assert.Null(result); + } + + private sealed class TestDto + { + public string? Name { get; init; } + } +} diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProvider.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProvider.cs index f7ed9f18..844b988d 100644 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProvider.cs +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/SubmodelRegistryProvider/Services/SubmodelDescriptorProvider.cs @@ -1,13 +1,18 @@ using System.Net; using System.Text.Json; +using System.Text.Json.Nodes; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry.Providers; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients; +using AAS.TwinEngine.DataEngine.Infrastructure.Shared; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; +using AasCore.Aas3_1; + using UnauthorizedAccessException = AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure.UnauthorizedAccessException; namespace AAS.TwinEngine.DataEngine.Infrastructure.Providers.SubmodelRegistryProvider.Services; @@ -34,8 +39,11 @@ public async Task GetDataForSubmodelDescriptorByIdAsync(stri try { - var descriptor = JsonSerializer.Deserialize(responseContent); - if (descriptor != null) + var jsonNode = JsonNode.Parse(responseContent); + var descriptorNode = jsonNode?["result"] ?? jsonNode; + var descriptor = DeserializeSubmodelDescriptor(descriptorNode); + + if (descriptor is not null) { return descriptor; } @@ -50,6 +58,27 @@ public async Task GetDataForSubmodelDescriptorByIdAsync(stri } } + private static SubmodelDescriptor? DeserializeSubmodelDescriptor(JsonNode? descriptorNode) + { + if (descriptorNode is null) + { + return null; + } + + return new SubmodelDescriptor + { + Description = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["description"], Jsonization.Deserialize.LangStringTextTypeFrom), + DisplayName = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["displayName"], Jsonization.Deserialize.LangStringNameTypeFrom), + Extensions = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["extensions"], Jsonization.Deserialize.ExtensionFrom), + Administration = AasJsonNodeDeserializer.DeserializeAasNode(descriptorNode["administration"], Jsonization.Deserialize.AdministrativeInformationFrom), + IdShort = descriptorNode["idShort"]?.GetValue(), + Id = descriptorNode["id"]?.GetValue(), + SemanticId = AasJsonNodeDeserializer.DeserializeAasNode(descriptorNode["semanticId"], Jsonization.Deserialize.ReferenceFrom), + SupplementalSemanticId = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["supplementalSemanticId"], Jsonization.Deserialize.ReferenceFrom), + Endpoints = descriptorNode["endpoints"]?.Deserialize>() + }; + } + private async Task ProcessResponseAsync(HttpResponseMessage response, string url, CancellationToken cancellationToken) { logger.LogInformation("Sending HTTP GET request to {Url}", url); diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/TemplateProvider.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/TemplateProvider.cs index c8ae1477..3f294022 100644 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/TemplateProvider.cs +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/TemplateProvider/Services/TemplateProvider.cs @@ -5,7 +5,10 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients; +using AAS.TwinEngine.DataEngine.Infrastructure.Shared; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; using AasCore.Aas3_1; @@ -55,8 +58,11 @@ public async Task GetShellDescriptorTemplateAsync(string templa try { - var descriptor = JsonSerializer.Deserialize(content); - if (descriptor != null) + var jsonNode = JsonNode.Parse(content); + var descriptorNode = jsonNode?["result"] ?? jsonNode; + var descriptor = DeserializeShellDescriptor(descriptorNode); + + if (descriptor is not null) { return descriptor; } @@ -232,5 +238,29 @@ private async Task SendGetRequestAsync(string url, string h } } + private static ShellDescriptor? DeserializeShellDescriptor(JsonNode? descriptorNode) + { + if (descriptorNode is null) + { + return null; + } + + return new ShellDescriptor + { + Description = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["description"], Jsonization.Deserialize.LangStringTextTypeFrom), + DisplayName = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["displayName"], Jsonization.Deserialize.LangStringNameTypeFrom), + Extensions = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["extensions"], Jsonization.Deserialize.ExtensionFrom), + Administration = AasJsonNodeDeserializer.DeserializeAasNode(descriptorNode["administration"], Jsonization.Deserialize.AdministrativeInformationFrom), + AssetKind = AasJsonNodeDeserializer.DeserializeEnum(descriptorNode["assetKind"]), + AssetType = AasJsonNodeDeserializer.DeserializeEnum(descriptorNode["assetType"]), + Endpoints = descriptorNode["endpoints"]?.Deserialize>(), + GlobalAssetId = descriptorNode["globalAssetId"]?.GetValue(), + IdShort = descriptorNode["idShort"]?.GetValue(), + Id = descriptorNode["id"]?.GetValue(), + SpecificAssetIds = AasJsonNodeDeserializer.DeserializeAasArray(descriptorNode["specificAssetIds"], Jsonization.Deserialize.SpecificAssetIdFrom), + SubmodelDescriptors = descriptorNode["submodelDescriptors"]?.Deserialize>() + }; + } + private static void UpdateSubmodelTemplateKind(ISubmodel submodel) => submodel.Kind = ModellingKind.Instance; } diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/AasJsonNodeDeserializer.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/AasJsonNodeDeserializer.cs new file mode 100644 index 00000000..a66f065f --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Infrastructure/Shared/AasJsonNodeDeserializer.cs @@ -0,0 +1,49 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace AAS.TwinEngine.DataEngine.Infrastructure.Shared; + +public static class AasJsonNodeDeserializer +{ + public static JsonNode? UnwrapResult(JsonNode? rootNode) => rootNode?["result"] ?? rootNode; + + public static T? DeserializeAasNode(JsonNode? node, Func deserializer) + where T : class + => node is null ? null : deserializer(node); + + public static List? DeserializeAasArray(JsonNode? node, Func deserializer) + where T : class + { + if (node is not JsonArray array) + { + return null; + } + + return [.. array.Select(item => item is null ? null : deserializer(item)) + .OfType()]; + } + + public static T? DeserializeObject(JsonNode? node, JsonSerializerOptions? options = null) + { + if (node is null) + { + return default; + } + + return options is null + ? node.Deserialize(JsonSerializationOptions.DeserializationOption) + : node.Deserialize(options); + } + + public static TEnum? DeserializeEnum(JsonNode? node) + where TEnum : struct, Enum + { + if (node is null) + { + return null; + } + + var value = node.GetValue(); + return Enum.TryParse(value, true, out var enumValue) ? enumValue : null; + } +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_Contact_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_Contact_Expected.json index 4f6190a8..ab29e600 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_Contact_Expected.json +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_Contact_Expected.json @@ -6,9 +6,14 @@ "idShort": "ContactInformations", "id": "https://mm-software.com/submodel/000-001/ContactInformation", "semanticId": { - "type": "ExternalReference", + "type": "ModelReference", "referredSemanticId": null, - "keys": null + "keys": [ + { + "type": "Submodel", + "value": "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations" + } + ] }, "supplementalSemanticId": null, "endpoints": [ diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_CustomSubmodel_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_CustomSubmodel_Expected.json index c82759af..3bc72a66 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_CustomSubmodel_Expected.json +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_CustomSubmodel_Expected.json @@ -1,8 +1,8 @@ { "description": [ { - "language": null, - "text": null + "language": "en", + "text": "The Submodel HierarchicalStructures identified by its semanticId. The Submodel idShort can be picked freely." } ], "displayName": null, @@ -13,7 +13,12 @@ "semanticId": { "type": "ExternalReference", "referredSemanticId": null, - "keys": null + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/CustomSubmodel/Submodel/Template/0/1" + } + ] }, "supplementalSemanticId": null, "endpoints": [ diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_HandoverDocumentation_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_HandoverDocumentation_Expected.json index 91f10035..8695ba29 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_HandoverDocumentation_Expected.json +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/SubmodelRegistry/TestData/GetSubmodelDescriptorById_HandoverDocumentation_Expected.json @@ -1,25 +1,30 @@ { "description": [ { - "language": null, - "text": null + "language": "en", + "text": "The Submodel defines a set meta data for the handover of documentation from the manufacturer to the operator for industrial equipment" } ], "displayName": null, "extensions": null, "administration": { "embeddedDataSpecifications": null, - "version": null, - "revision": null, + "version": "2", + "revision": "0", "creator": null, - "templateId": null + "templateId": "https://admin-shell.io/idta-02004-2-0" }, "idShort": "HandoverDocumentation", "id": "https://mm-software.com/submodel/000-001/HandoverDocumentation", "semanticId": { - "type": "ExternalReference", + "type": "ModelReference", "referredSemanticId": null, - "keys": null + "keys": [ + { + "type": "Submodel", + "value": "0173-1#01-AHF578#003" + } + ] }, "supplementalSemanticId": null, "endpoints": [