Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
614b82f
Enhance Shell Retrieval with IdShort Filtering
mm-kgi Jul 2, 2026
28753b0
Merge branch 'develop' into 1269-support-idshort-get-shells
mm-kgi Jul 2, 2026
3f38adc
Refactor shell retrieval endpoints to support idShort filtering and e…
mm-kgi Jul 2, 2026
1a89095
Merge branch 'develop' into 1269-support-idshort-get-shells
mm-kgi Jul 2, 2026
32d308b
Added the implementation of get all submodels endpoint
mm-kgi Jul 8, 2026
c370eed
merge with parent branch
mm-kgi Jul 8, 2026
68e1e1a
Added the level and extent as query param for specific submodel endpoint
mm-kgi Jul 8, 2026
2c5d1ff
remove unnecessary cast to 'IEnumerable<ISubmodel>'.
mm-kgi Jul 8, 2026
10eeacb
Potential fix for pull request finding
mm-kgi Jul 8, 2026
cdc7660
Potential fix for pull request finding
mm-kgi Jul 8, 2026
dcacc76
Potential fix for pull request finding
mm-kgi Jul 8, 2026
e552257
Potential fix for pull request finding
mm-kgi Jul 8, 2026
51a531b
Potential fix for pull request finding
mm-kgi Jul 8, 2026
31371c4
Potential fix for pull request finding
mm-kgi Jul 8, 2026
f02b13c
Potential fix for pull request finding
mm-kgi Jul 8, 2026
b175e3d
Potential fix for pull request finding
mm-kgi Jul 8, 2026
33bd2ce
Potential fix for pull request finding
mm-kgi Jul 8, 2026
7e29e73
Refactor and add unit tests for submodel service
mm-kgi Jul 9, 2026
ce97368
Remove unused ContactInformation template mapping from appsettings.de…
mm-kgi Jul 9, 2026
4a3d4b3
git merge parent branch
mm-kgi Jul 11, 2026
2af193a
Enhance SubmodelRepositoryService to support concurrent operations wi…
mm-kgi Jul 11, 2026
fbc7a11
Add TemplateManagementConfig to SubmodelRepositoryService
mm-kgi Jul 11, 2026
03721ad
Add TemplateManagementConfig options to SubmodelRepositoryServiceTests
mm-kgi Jul 11, 2026
426426b
Add ConcurrentOperationsLimit to service instances and validate confi…
mm-kgi Jul 12, 2026
743700c
Sanitize URLs in logging for HTTP requests in TemplateProvider
mm-kgi Jul 13, 2026
06608ee
Refactor SubmodelRepositoryController and related tests to enhance Ge…
mm-kgi Jul 13, 2026
20318ae
Add GetAllSubmodelsControllerTests to validate GetAllSubmodelsAsync f…
mm-kgi Jul 13, 2026
4fe003f
Update SubmodelRepositoryControllerTests to use GetFilteredSubmodelTe…
mm-kgi Jul 13, 2026
12cb390
Refactor SubmodelTemplateService and related tests to use GetFiltered…
mm-kgi Jul 13, 2026
4ec0137
Refactor various services to improve code clarity and consistency, in…
mm-kgi Jul 13, 2026
6c8a8e0
Add Get All Submodels - by idShort endpoint definition
mm-kgi Jul 14, 2026
6f2f0a8
merge parent branch
mm-kgi Jul 15, 2026
f6deb25
refactor the template provider tests
mm-kgi Jul 15, 2026
437e11d
Implement Get All Submodel Elements endpoint with associated request …
mm-kgi Jul 15, 2026
d1143c8
Refactor query parameter handling in GetAllSubmodelElementsController…
mm-kgi Jul 16, 2026
68d0734
git merge with parent branch
mm-kgi Jul 16, 2026
02adb1d
merge parent branch
mm-kgi Jul 16, 2026
6e8dd2a
Implement GetFileAttachment functionality with associated tests and c…
mm-kgi Jul 20, 2026
30b758d
Add file size validation and exception handling for file attachments
mm-omdi Jul 28, 2026
404a656
merged Develop branch
mm-omdi Jul 28, 2026
5aee804
Remove unused GetAllSubmodelElements method and SubmodelElementsDto m…
mm-omdi Jul 28, 2026
c0a5aa8
Add file attachment handling and tracing to SubmodelRepositoryService
mm-omdi Jul 28, 2026
7b60d43
Refactor file attachment handling in SubmodelRepositoryService to red…
mm-omdi Jul 29, 2026
ba48000
Add unit tests for GetFileAttachmentAsync in SubmodelRepositoryServic…
mm-omdi Jul 29, 2026
2c04229
Refactor file attachment handling to remove return types and simplify…
mm-omdi Jul 30, 2026
5c58316
Enhance file attachment handling by updating method signatures to ret…
mm-omdi Jul 30, 2026
d1d6d36
Refactor file attachment handling: update SubmodelRepositoryHandler a…
mm-omdi Jul 31, 2026
9224333
Implement file attachment streaming: add IFileAttachmentStreamProvide…
mm-hsh Jul 31, 2026
9dcb7f4
Refactor file attachment handling: introduce TemplateServices and Plu…
mm-omdi Aug 3, 2026
a04062c
Refactor file attachment handling: update exception types in GetFileA…
mm-omdi Aug 3, 2026
05c3513
Refactor file attachment handling: update documentation for file down…
mm-omdi Aug 3, 2026
fe45571
Updated API collections in both main and test plugin directories
mm-omdi Aug 3, 2026
5b21877
Add MaxLengthStream class for file size enforcement in streaming
mm-omdi Aug 3, 2026
b5f4a57
Add unit tests for FileAttachmentStreamProvider and MaxLengthStream
mm-omdi Aug 3, 2026
885de73
Enhance error logging in SubmodelRepositoryService: added logging for…
mm-omdi Aug 3, 2026
be1b494
Refactor file attachment handling: introduce IFileContentProvider int…
mm-omdi Aug 4, 2026
a0968be
Refactor SubmodelRepositoryControllerTests to use IFileContentProvide…
mm-omdi Aug 4, 2026
c3d5a86
File attachment review findings and discussion
mm-asha Aug 4, 2026
6b78b64
Implement file attachment handling improvements: add LimitedFileStrea…
mm-omdi Aug 5, 2026
1e1a7c2
Refactor file attachment handling in tests: update SubmodelRepository…
mm-omdi Aug 5, 2026
97f7dd3
Refactor file attachment handling: update GetFileAttachmentAsync meth…
mm-omdi Aug 5, 2026
95edfae
Refactor to use FileContentStreamResult for attachments
mm-omdi Aug 5, 2026
0995c0d
Refactor error handling in GetFileContentAsync method: replace Ensure…
mm-omdi Aug 5, 2026
d3df843
Refactor file attachment handling: remove commented-out sections in t…
mm-omdi Aug 5, 2026
0214233
updated module test
mm-omdi Aug 5, 2026
ab12cfd
refactor the exceptions handling in the submodel repository service
mm-kgi Aug 6, 2026
944c026
Refactor GlobalExceptionHandler: streamline error handling by consoli…
mm-kgi Aug 6, 2026
31514bf
Refactor GetErrorDetails method: streamline exception handling by con…
mm-kgi Aug 6, 2026
178b388
Refactor FileContentResponse: simplify constructor by removing conten…
mm-omdi Aug 7, 2026
9b77c64
Removed unused variable
mm-omdi Aug 7, 2026
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
@@ -0,0 +1,21 @@
meta {
name: Get Submodel Element File Attachment - HandoverDocumentation
type: http
seq: 5
}

get {
url: {{DataEngineBaseUrl}}/submodels/:submodelIdentifier/submodel-elements/:idShortPath/attachment
body: none
auth: inherit
}

params:path {
submodelIdentifier: {{submodelIdentifierHandoverDocumentation-1}}
idShortPath: Documents[0].DocumentVersions[0].DigitalFiles[0]
}

settings {
encodeUrl: true
timeout: 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
meta {
name: Submodel File Attachment
}
6 changes: 3 additions & 3 deletions example/postgres/seed/01_core_asset_tables.sql.inc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ TRUE,
5,
'2025-12-24T14:30:00Z',
'2035-12-24T14:30:00Z',
('https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/' ||
('https://raw.githubusercontent.com/AAS-TwinEngine/' ||
'AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf')
),

Expand Down Expand Up @@ -98,7 +98,7 @@ FALSE,
2.2999999999999998,
'2026-01-15T09:15:00+05:30',
'2036-01-15T09:15:00+05:31',
('https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/' ||
('https://raw.githubusercontent.com/AAS-TwinEngine/' ||
'AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf')
),

Expand Down Expand Up @@ -144,7 +144,7 @@ TRUE,
7.8,
'2024-07-01T18:45:00-04:00',
'2034-07-01T18:45:00-04:01',
('https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/' ||
('https://raw.githubusercontent.com/AAS-TwinEngine/' ||
'AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf')
);

Expand Down
18 changes: 9 additions & 9 deletions example/postgres/seed/04_handoverdocumentation.sql.inc
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ INSERT INTO "DocumentVersion" (
) VALUES
(
0,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'1',
'2023-01-01',
Expand All @@ -108,7 +108,7 @@ INSERT INTO "DocumentVersion" (

(
1,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'1.1',
'2024-05-05',
Expand All @@ -129,7 +129,7 @@ INSERT INTO "DocumentVersion" (

(
0,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'2.1',
'2026-01-01',
Expand All @@ -150,7 +150,7 @@ INSERT INTO "DocumentVersion" (

(
0,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'2.3',
'2025-10-10',
Expand All @@ -171,7 +171,7 @@ INSERT INTO "DocumentVersion" (

(
0,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'0.9',
'2024-01-01',
Expand All @@ -192,7 +192,7 @@ INSERT INTO "DocumentVersion" (

(
0,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'1.2',
'2024-03-03',
Expand All @@ -213,7 +213,7 @@ INSERT INTO "DocumentVersion" (

(
1,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'1.4',
'2023-01-01',
Expand All @@ -234,7 +234,7 @@ INSERT INTO "DocumentVersion" (

(
2,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'2',
'2024-03-03',
Expand All @@ -255,7 +255,7 @@ INSERT INTO "DocumentVersion" (

(
0,
'https://docs.google.com/viewer?url=https://raw.githubusercontent.com/'
'https://raw.githubusercontent.com/'
'AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf',
'1',
'2022-02-02',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Nodes;
Expand All @@ -7,6 +8,7 @@
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin.Providers;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository.Providers;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients;
using AAS.TwinEngine.DataEngine.ModuleTests.Common;

Expand All @@ -18,6 +20,7 @@

using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config;
using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository;
using AAS.TwinEngine.DataEngine.DomainModel.Shared;

namespace AAS.TwinEngine.DataEngine.ModuleTests.Api.Services.SubmodelRepository;

Expand All @@ -27,20 +30,23 @@ public abstract class SubmodelRepositoryControllerTests : IDisposable
private readonly ITemplateProvider _mockTemplateProvider;
private readonly HttpClient _client;
private readonly ICreateClient _httpClientFactory;
private readonly IFileContentProvider _fileContentProvider;

protected SubmodelRepositoryControllerTests(string configDir)
{
_mockTemplateProvider = Substitute.For<ITemplateProvider>();
var mockPluginManifestProvider = Substitute.For<IPluginManifestProvider>();
var mockPluginManifestConflictHandler = Substitute.For<IPluginManifestConflictHandler>();
_httpClientFactory = Substitute.For<ICreateClient>();
_fileContentProvider = Substitute.For<IFileContentProvider>();

_factory = new ConfigTestFactory(configDir, services =>
{
_ = services.AddSingleton(_httpClientFactory);
_ = services.AddSingleton(_mockTemplateProvider);
_ = services.AddSingleton(mockPluginManifestProvider);
_ = services.AddSingleton(mockPluginManifestConflictHandler);
_ = services.AddSingleton(_fileContentProvider);
});

_client = _factory.CreateClient();
Expand Down Expand Up @@ -180,6 +186,72 @@ public async Task GetSubmodelElementAsync_WithNotFound_Returns404Async()
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task GetFileAttachmentAsync_WhenElementIsFileWithHttpUrl_StreamsContentAsync()
{
// Arrange
const string SubmodelId = "Q29udGFjdEluZm9ybWF0aW9u";
const string IdShortPath = "Thumbnail";
const string FileUrl = "https://example.com/logo.png";
var fileBytes = Encoding.UTF8.GetBytes("fake-image-bytes");

using var messageHandler = new FakeHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(TestData.CreatePluginResponseForFileAttachment())
}));

using var httpClient = new HttpClient(messageHandler);
httpClient.BaseAddress = new Uri("https://testendpoint.com");

const string HttpClientName = $"{HttpClientNames.PluginDataProviderPrefix}TestPlugin1";
_ = _httpClientFactory.CreateClient(HttpClientName).Returns(httpClient);

_ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any<string>(), Arg.Any<SubmodelQueryOptions?>(), Arg.Any<CancellationToken>()).Returns(TestData.CreateSubmodel());

var fileContentResponse = new FileContentResponse(new MemoryStream(fileBytes));
_ = _fileContentProvider.GetFileContentAsync(FileUrl, Arg.Any<CancellationToken>()).Returns(fileContentResponse);

// Act
var response = await _client.GetAsync($"/submodels/{SubmodelId}/submodel-elements/{IdShortPath}/attachment");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("image/png", response.Content.Headers.ContentType?.MediaType);
var body = await response.Content.ReadAsByteArrayAsync();
Assert.Equal(fileBytes, body);
Assert.Contains("logo.png", response.Content.Headers.ContentDisposition?.ToString(), StringComparison.Ordinal);
await _fileContentProvider.Received(1).GetFileContentAsync(FileUrl, Arg.Any<CancellationToken>());
}

[Fact]
public async Task GetFileAttachmentAsync_WhenElementIsNotFile_Returns400Async()
{
// Arrange
const string SubmodelId = "Q29udGFjdEluZm9ybWF0aW9u";
const string IdShortPath = "ContactName";

using var messageHandler = new FakeHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(TestData.CreatePluginResponseForSubmodelElement())
}));

using var httpClient = new HttpClient(messageHandler);
httpClient.BaseAddress = new Uri("https://testendpoint.com");

const string HttpClientName = $"{HttpClientNames.PluginDataProviderPrefix}TestPlugin1";
_ = _httpClientFactory.CreateClient(HttpClientName).Returns(httpClient);

_ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any<string>(), Arg.Any<SubmodelQueryOptions?>(), Arg.Any<CancellationToken>()).Returns(TestData.CreateSubmodel());

// Act
var response = await _client.GetAsync($"/submodels/{SubmodelId}/submodel-elements/{IdShortPath}/attachment");

// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Fact]
public async Task GetSubmodelElementAsync_WhenIdentifierIsInValid_Returns400Async()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ public static string CreatePluginResponseForSubmodelElement() => """
}
""";

public static string CreatePluginResponseForFileAttachment() => """
{
"http://example.com/idta/digital-nameplate/semantic-id": {
"http://example.com/idta/digital-nameplate/thumbnail": "https://example.com/logo.png"
}
}
""";

public static string CreatePlugin1ResponseForSubmodel() => """
{
"http://example.com/idta/digital-nameplate/semantic-id": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Text;
using System.Text;

using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Handler;
using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository;
using AAS.TwinEngine.DataEngine.DomainModel.Shared;
using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository;

using AasCore.Aas3_1;
Expand All @@ -15,13 +16,17 @@
using NSubstitute;

namespace AAS.TwinEngine.DataEngine.UnitTests.Api.SubmodelRepository.Handler;

public class SubmodelRepositoryHandlerTests
{
private readonly ISubmodelRepositoryService _submodelRepository = Substitute.For<ISubmodelRepositoryService>();
private readonly ILogger<SubmodelRepositoryHandler> _logger = Substitute.For<ILogger<SubmodelRepositoryHandler>>();
private readonly SubmodelRepositoryHandler _sut;

public SubmodelRepositoryHandlerTests() => _sut = new SubmodelRepositoryHandler(_logger, _submodelRepository);
public SubmodelRepositoryHandlerTests()
{
_sut = new SubmodelRepositoryHandler(_logger, _submodelRepository);
}

[Fact]
public async Task HandleSubmodel_ReturnsSubmodel_WhenSubmodelExists()
Expand Down Expand Up @@ -344,7 +349,7 @@ await _submodelRepository.Received(1).GetAllSubmodelsAsync(
[Fact]
public async Task GetAllSubmodels_WhenNullRequest_ReturnsSuccessfully()
{
var submodelList = new SubmodelList { PagingMetaData = new DomainModel.Shared.PagingMetaData(), Result = [] };
var submodelList = new SubmodelList { PagingMetaData = new PagingMetaData(), Result = [] };
_submodelRepository.GetAllSubmodelsAsync(Arg.Any<SubmodelSearchFilter?>(), Arg.Any<SubmodelQueryOptions?>(), null, null, Arg.Any<CancellationToken>())
.Returns(submodelList);

Expand Down Expand Up @@ -452,4 +457,62 @@ await _submodelRepository.Received(1)
.GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any<CancellationToken>());
}

[Fact]
public async Task GetFileAttachment_CallsService_WhenServiceSucceeds()
{
const string SubmodelId = "NameplateSubmodel";
const string IdShortPath = "Documents.ProductImage";
var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId));
var request = new GetSubmodelElementRequest(encodedId, IdShortPath);
var fakeResult = new FileAttachmentResult(Stream.Null, "application/octet-stream", "file.bin", 100 * 1024 * 1024);
_submodelRepository
.GetFileAttachmentAsync(SubmodelId, IdShortPath, Arg.Any<CancellationToken>())
.Returns(fakeResult);

await _sut.GetFileAttachment(request, CancellationToken.None);

await _submodelRepository.Received(1)
.GetFileAttachmentAsync(SubmodelId, IdShortPath, Arg.Any<CancellationToken>());
}

[Fact]
public async Task GetFileAttachment_ReturnsServiceResult_WhenServiceSucceeds()
{
const string SubmodelId = "NameplateSubmodel";
const string IdShortPath = "Documents.ProductImage";
var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId));
var request = new GetSubmodelElementRequest(encodedId, IdShortPath);

var fakeResult = new FileAttachmentResult(Stream.Null, "application/octet-stream", "file.bin", 100 * 1024 * 1024);

_submodelRepository
.GetFileAttachmentAsync(SubmodelId, IdShortPath, Arg.Any<CancellationToken>())
.Returns(fakeResult);

var result = await _sut.GetFileAttachment(request, CancellationToken.None);

Assert.Equal(fakeResult, result);
}

[Fact]
public async Task GetFileAttachment_InvalidBase64SubmodelId_ThrowsInvalidUserInputException()
{
const string InvalidEncodedId = "!!invalid_base64@@";
const string IdShortPath = "Documents.ProductImage";
var request = new GetSubmodelElementRequest(InvalidEncodedId, IdShortPath);

await Assert.ThrowsAsync<InvalidUserInputException>(() => _sut.GetFileAttachment(request, CancellationToken.None));
}

[Theory]
[InlineData("../../../etc/passwd")]
[InlineData("..\\..\\..\\windows\\system32")]
public async Task GetFileAttachment_PathTraversalInIdShortPath_ThrowsInvalidUserInputException(string maliciousPath)
{
const string SubmodelId = "NameplateSubmodel";
var encodedId = SubmodelId.EncodeBase64Url();
var request = new GetSubmodelElementRequest(encodedId, maliciousPath);

await Assert.ThrowsAsync<InvalidUserInputException>(() => _sut.GetFileAttachment(request, CancellationToken.None));
}
}
Loading
Loading