Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 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
190ce53
feat: implement GetThumbnail endpoint and service logic to retrieve a…
mm-kgi Aug 3, 2026
fe45571
Updated API collections in both main and test plugin directories
mm-omdi Aug 3, 2026
0815370
Merge branch '1272-file-attechment-submodel' into 1273-get-thumbnail-…
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
65ecb91
Merge branch '1272-file-attechment-submodel' into 1273-get-thumbnail-…
mm-omdi Aug 3, 2026
01e9ff9
Refactor AasRepositoryService and update MaxLengthStream namespace; s…
mm-omdi Aug 4, 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
00ecbd9
Merge branch '1272-file-attechment-submodel' into 1273-get-thumbnail-…
mm-omdi Aug 5, 2026
327b94e
Refactor thumbnail streaming and error handling
mm-omdi Aug 5, 2026
2fbb546
Merge branch 'develop' into 1273-get-thumbnail-attechment-shell
mm-omdi Aug 7, 2026
a7bd89e
Refactor thumbnail handling in tests: replace IFileAttachmentStreamPr…
mm-omdi Aug 7, 2026
da1f4e8
Refactor submodel repository handling: replace SubmodelRepository.Pro…
mm-omdi Aug 7, 2026
730f228
Fix submodel retrieval by removing unnecessary type conversion and cl…
mm-omdi Aug 7, 2026
e186c38
Refactor thumbnail content type handling in AasRepositoryControllerTe…
mm-omdi Aug 7, 2026
e5539a3
Merge branch 'develop' into 1273-get-thumbnail-attechment-shell
mm-hsh Aug 10, 2026
e17ef23
Refactor exception handling and logging in AasRepositoryService and r…
mm-omdi Aug 10, 2026
be34c1f
Refactor thumbnail handling in AasRepositoryControllerTests: replace …
mm-omdi Aug 10, 2026
9a87d4c
Add FileContentProvider and associated tests; refactor thumbnail cont…
mm-omdi Aug 11, 2026
46484fa
Implement FileContentStreamResult with ContentDispositionType; add un…
mm-omdi Aug 11, 2026
02499d3
Refactor GetThumbnailAsync method and update ContentDispositionType; …
mm-omdi Aug 11, 2026
e2c8ed4
Updated imports in the controller
mm-omdi Aug 11, 2026
423b62a
Add missing using directive for AAS.TwinEngine.DataEngine.Api.Shared …
mm-omdi Aug 11, 2026
f3823e3
Refactor content disposition handling in FileContentStreamResult to u…
mm-omdi Aug 12, 2026
cb74305
Fix ContentDispositionType handling in FileContentStreamResult to ens…
mm-omdi Aug 12, 2026
7c68145
Removed extra space
mm-omdi Aug 12, 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,20 @@
meta {
name: Get Thumbnail By Id
type: http
seq: 2
}

get {
url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/asset-information/thumbnail
body: none
auth: inherit
}

params:path {
aasIdentifier: {{aasIdentifier-1}}
}

settings {
encodeUrl: true
timeout: 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
meta {
name: Get Thumbnail By Id
type: http
seq: 2
}

get {
url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/asset-information/thumbnail
body: none
auth: inherit
}

params:path {
aasIdentifier: {{aasIdentifier-2}}
}

settings {
encodeUrl: true
timeout: 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
meta {
name: Get Thumbnail By Id
type: http
seq: 2
}

get {
url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/asset-information/thumbnail
body: none
auth: inherit
}

params:path {
aasIdentifier: {{aasIdentifier-3}}
}

settings {
encodeUrl: true
timeout: 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
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.Shared.Providers;
using AAS.TwinEngine.DataEngine.DomainModel.Shared;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients;
using AAS.TwinEngine.DataEngine.ModuleTests.Common;
using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config;
Expand All @@ -26,12 +28,14 @@ public abstract class AasRepositoryControllerTests : IDisposable
{
private readonly ConfigTestFactory _factory;
private readonly ITemplateProvider _mockTemplateProvider;
private readonly IFileContentProvider _fileContentProvider;
private readonly HttpClient _client;
private readonly ICreateClient _httpClientFactory;

protected AasRepositoryControllerTests(string configDir)
{
_mockTemplateProvider = Substitute.For<ITemplateProvider>();
_fileContentProvider = Substitute.For<IFileContentProvider>();
var mockPluginManifestProvider = Substitute.For<IPluginManifestProvider>();
var mockPluginManifestConflictHandler = Substitute.For<IPluginManifestConflictHandler>();
_httpClientFactory = Substitute.For<ICreateClient>();
Expand All @@ -42,6 +46,7 @@ protected AasRepositoryControllerTests(string configDir)
_ = services.AddSingleton(mockPluginManifestConflictHandler);
_ = services.AddSingleton(_httpClientFactory);
_ = services.AddSingleton(_mockTemplateProvider);
_ = services.AddSingleton(_fileContentProvider);
});

_client = _factory.CreateClient();
Expand Down Expand Up @@ -549,6 +554,83 @@ private void SetupTemplateProvider()
});
}

[Fact]
public async Task GetThumbnailAsync_ShouldReturn200OKWithStream_WhenThumbnailExists()
{
const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg=";

var messageHandler = new FakeHttpMessageHandler((request, token) =>
{
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(TestData.CreatePluginResponseForAssetinformation())
};
return Task.FromResult(httpResponse);
});

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

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

var expectedAssetInformation = TestData.CreateAssetInformationTemplate();
_ = _mockTemplateProvider.GetAssetInformationTemplateAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(expectedAssetInformation);

var stream = new MemoryStream("test-bytes"u8.ToArray());
var fileContentResponse = new FileContentResponse(stream);
_ = _fileContentProvider.GetFileContentAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(fileContentResponse);

var response = await _client.GetAsync($"/shells/{AasIdentifier}/asset-information/thumbnail");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expectedAssetInformation.DefaultThumbnail.ContentType, response.Content.Headers.ContentType?.MediaType);
var bytes = await response.Content.ReadAsByteArrayAsync();
Assert.Equal("test-bytes"u8.ToArray(), bytes);
}

[Fact]
public async Task GetThumbnailAsync_ShouldReturn404_WhenThumbnailIsMissing()
{
const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg=";

var mockAssetInformationTemplate = new AssetInformation(
AssetKind.Instance,
"https://example.com/ids/asset/123",
[],
defaultThumbnail: null
);

var messageHandler = new FakeHttpMessageHandler((request, token) =>
{
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("""
{
"assetKind": "Type",
"globalAssetId": "https://example.com/ids/asset/123",
"specificAssetIds": [],
"defaultThumbnail": null
}
""")
};
return Task.FromResult(httpResponse);
});

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.GetAssetInformationTemplateAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(mockAssetInformationTemplate);

var response = await _client.GetAsync($"/shells/{AasIdentifier}/asset-information/thumbnail");

Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

private static string EncodeBase64Url(string plainText)
{
if (string.IsNullOrWhiteSpace(plainText))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +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.ApplicationLogic.Services.Shared.Providers;
using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients;
using AAS.TwinEngine.DataEngine.ModuleTests.Common;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests;
using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses;
using AAS.TwinEngine.DataEngine.Api.Shared;
using AAS.TwinEngine.DataEngine.Api.Shared.Results;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions;
using AAS.TwinEngine.DataEngine.DomainModel.Shared;

using AasCore.Aas3_1;

Expand Down Expand Up @@ -274,5 +276,32 @@ private static SubmodelRefDto CreateSubmodelRefDto()
};
}

[Fact]
public async Task GetThumbnailAsync_ShouldReturnFileContentStreamResult_WhenHandlerCompletes()
{
var expectedStream = new MemoryStream("test-image"u8.ToArray());
var attachmentResult = new FileAttachmentResult(expectedStream, "image/png", "thumbnail.png", 100 * 1024 * 1024);
_handler.GetThumbnailAsync(Arg.Any<GetThumbnailRequest>(), Arg.Any<CancellationToken>())
.Returns(attachmentResult);

var response = await _sut.GetThumbnailAsync(AasIdentifier, CancellationToken.None);

Assert.IsType<FileContentStreamResult>(response);
}

[Fact]
public async Task GetThumbnailAsync_PassesRouteValuesToHandler()
{
_handler.GetThumbnailAsync(Arg.Any<GetThumbnailRequest>(), Arg.Any<CancellationToken>())
.Returns(new FileAttachmentResult(Stream.Null, "image/png", "thumbnail.png", 100 * 1024 * 1024));

await _sut.GetThumbnailAsync(AasIdentifier, CancellationToken.None);

await _handler.Received(1)
.GetThumbnailAsync(
Arg.Is<GetThumbnailRequest>(r => r.AasIdentifier == AasIdentifier),
Arg.Any<CancellationToken>());
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

using AasCore.Aas3_1;

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

using NSubstitute;
Expand Down Expand Up @@ -249,6 +250,72 @@ public async Task GetSubmodelRefByIdAsync_InvalidBase64_ThrowsInvalidUserInputEx
await Assert.ThrowsAsync<InvalidUserInputException>(() => _sut.GetSubmodelRefByIdAsync(request, CancellationToken.None));
}

[Fact]
public async Task GetThumbnailAsync_ShouldReturnFileAttachmentResult()
{
var expectedResult = new FileAttachmentResult(new MemoryStream(), "image/png", "test.png", 100 * 1024 * 1024);
_aasRepositoryService.GetThumbnailAsync("https://example.com/aas", Arg.Any<CancellationToken>())
.Returns(expectedResult);

var request = new GetThumbnailRequest("aHR0cHM6Ly9leGFtcGxlLmNvbS9hYXM");
var result = await _sut.GetThumbnailAsync(request, CancellationToken.None);

Assert.Equal(expectedResult, result);
}

[Fact]
public async Task GetThumbnailAsync_CallsServiceWithDecodedAasIdentifier_WhenInputIsValid()
{
const string aasIdentifier = "https://example.com/aas";
var encodedId = aasIdentifier.EncodeBase64Url();
var request = new GetThumbnailRequest(encodedId);
var expectedResult = new FileAttachmentResult(Stream.Null, "image/png", "thumbnail.png", 100 * 1024 * 1024);

_aasRepositoryService
.GetThumbnailAsync(aasIdentifier, Arg.Any<CancellationToken>())
.Returns(expectedResult);

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

await _aasRepositoryService.Received(1)
.GetThumbnailAsync(aasIdentifier, Arg.Any<CancellationToken>());
}

[Fact]
public async Task GetThumbnailAsync_InvalidBase64AasIdentifier_ThrowsInvalidUserInputException()
{
const string invalidEncodedId = "!!invalid_base64@@";
var request = new GetThumbnailRequest(invalidEncodedId);

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

[Theory]
[InlineData("../../../etc/passwd")]
[InlineData("..\\..\\..\\windows\\system32")]
public async Task GetThumbnailAsync_MaliciousDecodedIdentifier_ThrowsInvalidUserInputException(string maliciousIdentifier)
{
var request = new GetThumbnailRequest(maliciousIdentifier.EncodeBase64Url());

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

[Fact]
public async Task GetThumbnailAsync_ServiceReturnsNull_ThrowsTemplateNotFoundException()
{
const string aasIdentifier = "https://example.com/aas";
var request = new GetThumbnailRequest(aasIdentifier.EncodeBase64Url());

_aasRepositoryService
.GetThumbnailAsync(aasIdentifier, Arg.Any<CancellationToken>())!
.Returns((FileAttachmentResult)null!);

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

private static AssetInformation CreateAssetInformation()
{
var thumbnail = Substitute.For<IResource>();
Expand Down
Loading
Loading