diff --git a/Modules/System Tests/Azure Blob Services API/src/ABSBlobClientTest.Codeunit.al b/Modules/System Tests/Azure Blob Services API/src/ABSBlobClientTest.Codeunit.al
index e38dc58202..6343d1d0c8 100644
--- a/Modules/System Tests/Azure Blob Services API/src/ABSBlobClientTest.Codeunit.al
+++ b/Modules/System Tests/Azure Blob Services API/src/ABSBlobClientTest.Codeunit.al
@@ -18,7 +18,7 @@ codeunit 132920 "ABS Blob Client Test"
Response: Codeunit "ABS Operation Response";
ContainerName, BlobName, BlobContent, NewBlobContent : Text;
begin
- // [Scenarion] Given a storage account and a container, PutBlobBlockBlob operation succeeds and GetBlobAsText returns the content
+ // [Scenario] Given a storage account and a container, PutBlobBlockBlob operation succeeds and GetBlobAsText returns the content
SharedKeyAuthorization := StorageServiceAuthorization.CreateSharedKey(AzuriteTestLibrary.GetAccessKey());
@@ -53,7 +53,7 @@ codeunit 132920 "ABS Blob Client Test"
Response: Codeunit "ABS Operation Response";
ContainerName, FirstBlobName, SecondBlobName, BlobContent : Text;
begin
- // [Scenarion] Given a storage account and a container with BLOBs, ListBlobs operation succeeds.
+ // [Scenario] Given a storage account and a container with BLOBs, ListBlobs operation succeeds.
SharedKeyAuthorization := StorageServiceAuthorization.CreateSharedKey(AzuriteTestLibrary.GetAccessKey());
@@ -101,7 +101,7 @@ codeunit 132920 "ABS Blob Client Test"
BlobList: Dictionary of [Text, XmlNode];
Blobs: List of [Text];
begin
- // [Scenarion] Given a storage account and a container with BLOBs, ListBlobs operation succeeds.
+ // [Scenario] Given a storage account and a container with BLOBs, ListBlobs operation succeeds.
SharedKeyAuthorization := StorageServiceAuthorization.CreateSharedKey(AzuriteTestLibrary.GetAccessKey());
@@ -200,7 +200,7 @@ codeunit 132920 "ABS Blob Client Test"
ABSContainerContent: Record "ABS Container Content";
ContainerName: Text;
begin
- // [Scenarion] When listing blobs, the levels, parent directories etc. are set correctly
+ // [Scenario] When listing blobs, the levels, parent directories etc. are set correctly
SharedKeyAuthorization := StorageServiceAuthorization.CreateSharedKey(AzuriteTestLibrary.GetAccessKey());
@@ -538,7 +538,7 @@ codeunit 132920 "ABS Blob Client Test"
LeaseId: Guid;
ProposedLeaseId: Guid;
begin
- // [Scenarion] Given a storage account and a container, PutBlobBlockBlob operation succeeds and subsequent lease-operations
+ // [Scenario] Given a storage account and a container, PutBlobBlockBlob operation succeeds and subsequent lease-operations
// (1) create a lease, (2) renew a lease, [(3) change a lease], (4) break a lease and (5) release the lease
SharedKeyAuthorization := StorageServiceAuthorization.CreateSharedKey(AzuriteTestLibrary.GetAccessKey());
@@ -665,6 +665,68 @@ codeunit 132920 "ABS Blob Client Test"
ABSContainerClient.DeleteContainer(ContainerName);
end;
+ [Test]
+ procedure ParseResponseWithHierarchicalBlobName()
+ var
+ ABSContainerContent: Record "ABS Container Content";
+ ABSHelperLibrary: Codeunit "ABS Helper Library";
+ NodeList: XmlNodeList;
+ NextMarker: Text;
+ begin
+ // [SCENARIO] Parse the BLOB storage API response listing BLOBs in a container without the hierarchical namespace
+
+ // [GIVEN] Prepare an XML response from the BLOB Storage API, listing all BLOBs in a container.
+ // [GIVEN] Container does not have hierarchical namespace and contains a single BLOB with the name like "folder/blob"
+ NodeList := ABSHelperLibrary.CreateBlobNodeListFromResponse(ABSTestLibrary.GetServiceResponseBlobWithHierarchicalName(), NextMarker);
+
+ // [WHEN] Invoke BlobNodeListToTempRecord
+ ABSHelperLibrary.BlobNodeListToTempRecord(NodeList, ABSContainerContent);
+
+ // [THEN] "ABS Container Content" contains one record "folder" and one record "blob"
+ Assert.RecordCount(ABSContainerContent, 2);
+
+ ABSContainerContent.SetRange(Name, ABSTestLibrary.GetSampleResponseRootDirName());
+ Assert.RecordCount(ABSContainerContent, 1);
+
+ ABSContainerContent.SetRange(Name, ABSTestLibrary.GetSampleResponseFileName());
+ Assert.RecordCount(ABSContainerContent, 1);
+ end;
+
+ [Test]
+ procedure ParseResponseFromStorageHierarachicalNamespace()
+ var
+ ABSContainerContent: Record "ABS Container Content";
+ ABSHelperLibrary: Codeunit "ABS Helper Library";
+ NodeList: XmlNodeList;
+ NextMarker: Text;
+ begin
+ // [SCENARIO] Parse the BLOB storage API response listing BLOBs in a container with the hierarchical namespace enabled
+
+ // [GIVEN] Prepare an XML response from the BLOB Storage API, listing all BLOBs in a container.
+ // [GIVEN] Container has the hierarchical namespace enabled, and contains a root folder named "rootdir".
+ // [GIVEN] There is a subdirectory "subdir" in the root, and one blob named "blob" in the subdirectory.
+ NodeList := ABSHelperLibrary.CreateBlobNodeListFromResponse(ABSTestLibrary.GetServiceResponseHierarchicalNamespace(), NextMarker);
+
+ // [WHEN] Invoke BlobNodeListToTempRecord
+ ABSHelperLibrary.BlobNodeListToTempRecord(NodeList, ABSContainerContent);
+
+ // [THEN] "ABS Container Content" contains two records with resource type "folder" and one record with resource type = "blob"
+ Assert.RecordCount(ABSContainerContent, 3);
+
+ VerifyContainerContentType(ABSContainerContent, CopyStr(ABSTestLibrary.GetSampleResponseRootDirName(), 1, MaxStrLen(ABSContainerContent.Name)), Enum::"ABS Blob Resource Type"::Directory);
+ VerifyContainerContentType(ABSContainerContent, CopyStr(ABSTestLibrary.GetSampleResponseSubdirName(), 1, MaxStrLen(ABSContainerContent.Name)), Enum::"ABS Blob Resource Type"::Directory);
+ VerifyContainerContentType(ABSContainerContent, CopyStr(ABSTestLibrary.GetSampleResponseFileName(), 1, MaxStrLen(ABSContainerContent.Name)), Enum::"ABS Blob Resource Type"::File);
+ end;
+
+ local procedure VerifyContainerContentType(var ABSContainerContent: Record "ABS Container Content"; BlobName: Text[2048]; ExpectedResourceType: Enum "ABS Blob Resource Type")
+ var
+ IncorrectBlobPropertyErr: Label 'BLOB property is assigned incorrectly';
+ begin
+ ABSContainerContent.SetRange(Name, BlobName);
+ ABSContainerContent.FindFirst();
+ Assert.AreEqual(ExpectedResourceType, ABSContainerContent."Resource Type", IncorrectBlobPropertyErr);
+ end;
+
local procedure GetBlobTagsFromABSContainerBlobList(BlobName: Text; BlobList: Dictionary of [Text, XmlNode]): Dictionary of [Text, Text]
var
BlobNode: XmlNode;
@@ -715,4 +777,4 @@ codeunit 132920 "ABS Blob Client Test"
ABSTestLibrary: Codeunit "ABS Test Library";
AzuriteTestLibrary: Codeunit "Azurite Test Library";
SharedKeyAuthorization: Interface "Storage Service Authorization";
-}
\ No newline at end of file
+}
diff --git a/Modules/System Tests/Azure Blob Services API/src/ABSTestLibrary.Codeunit.al b/Modules/System Tests/Azure Blob Services API/src/ABSTestLibrary.Codeunit.al
index d55eaf8fc0..48a903285a 100644
--- a/Modules/System Tests/Azure Blob Services API/src/ABSTestLibrary.Codeunit.al
+++ b/Modules/System Tests/Azure Blob Services API/src/ABSTestLibrary.Codeunit.al
@@ -156,6 +156,137 @@ codeunit 132921 "ABS Test Library"
exit(Document);
end;
+ procedure GetServiceResponseBlobWithHierarchicalName(): Text;
+ var
+ Builder: TextBuilder;
+ begin
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('rootdir/filename.txt');
+ Builder.Append('https://myaccount.blob.core.windows.net/mycontainer/rootdir/filename.txt');
+ Builder.Append('');
+ Builder.Append('Sat, 23 Sep 2023 21:32:55 GMT');
+ Builder.Append('0x8DBBC7CA6253661');
+ Builder.Append('1');
+ Builder.Append('text/plain');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('dpT0pmMW5TyM3Z2ZVL1hHQ==');
+ Builder.Append('');
+ Builder.Append('BlockBlob');
+ Builder.Append('unlocked');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+
+ exit(Builder.ToText());
+ end;
+
+ procedure GetServiceResponseHierarchicalNamespace(): Text;
+ var
+ Builder: TextBuilder;
+ begin
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('rootdir');
+ Builder.Append('');
+ Builder.Append('Sat, 23 Sep 2023 21:04:18 GMT');
+ Builder.Append('Sat, 23 Sep 2023 21:04:18 GMT');
+ Builder.Append('0x8DBBC78A6E95AF7');
+ Builder.Append('directory');
+ Builder.Append('0');
+ Builder.Append('application/octet-stream');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('AAAAAAAAAAA=');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('BlockBlob');
+ Builder.Append('Hot');
+ Builder.Append('true');
+ Builder.Append('unlocked');
+ Builder.Append('available');
+ Builder.Append('true');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('rootdir/subdirectory');
+ Builder.Append('');
+ Builder.Append('Tue, 26 Sep 2023 21:47:48 GMT');
+ Builder.Append('Tue, 26 Sep 2023 21:47:48 GMT');
+ Builder.Append('0x8DBBEDA39F9C41D');
+ Builder.Append('directory');
+ Builder.Append('0');
+ Builder.Append('application/octet-stream');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('AAAAAAAAAAA=');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('BlockBlob');
+ Builder.Append('Hot');
+ Builder.Append('true');
+ Builder.Append('unlocked');
+ Builder.Append('available');
+ Builder.Append('true');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('rootdir/subdirectory/filename.txt');
+ Builder.Append('');
+ Builder.Append('Wed, 27 Sep 2023 20:24:18 GMT');
+ Builder.Append('Wed, 27 Sep 2023 20:24:18 GMT');
+ Builder.Append('0x8DBBF97BA4A9839');
+ Builder.Append('file');
+ Builder.Append('1');
+ Builder.Append('text/plain');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('dpT0pmMW5TyM3Z2ZVL1hHQ==');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('BlockBlob');
+ Builder.Append('Hot');
+ Builder.Append('true');
+ Builder.Append('unlocked');
+ Builder.Append('available');
+ Builder.Append('true');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+ Builder.Append('');
+
+ exit(Builder.ToText());
+ end;
+
+ procedure GetSampleResponseRootDirName(): Text
+ begin
+ exit('rootdir');
+ end;
+
+ procedure GetSampleResponseSubdirName(): Text
+ begin
+ exit('subdirectory');
+ end;
+
+ procedure GetSampleResponseFileName(): Text
+ begin
+ exit('filename.txt');
+ end;
+
local procedure GetNewLineCharacter(): Text
var
LF: Char;
diff --git a/Modules/System/Azure Blob Services API/src/Helper/ABSContainerContentHelper.Codeunit.al b/Modules/System/Azure Blob Services API/src/Helper/ABSContainerContentHelper.Codeunit.al
index 4ed0928477..0b85302a8d 100644
--- a/Modules/System/Azure Blob Services API/src/Helper/ABSContainerContentHelper.Codeunit.al
+++ b/Modules/System/Azure Blob Services API/src/Helper/ABSContainerContentHelper.Codeunit.al
@@ -25,15 +25,19 @@ codeunit 9054 "ABS Container Content Helper"
Node.SelectSingleNode('.//Properties', PropertiesNode);
ChildNodes := PropertiesNode.AsXmlElement().GetChildNodes();
- AddNewEntry(ABSContainerContent, NameFromXml, OuterXml, ChildNodes, EntryNo);
+ AddNewEntry(ABSContainerContent, NameFromXml, OuterXml, ChildNodes, EntryNo, GetBlobResourceType(Node));
end;
[NonDebuggable]
- procedure AddNewEntry(var ABSContainerContent: Record "ABS Container Content"; NameFromXml: Text; OuterXml: Text; ChildNodes: XmlNodeList; var EntryNo: Integer)
+ procedure AddNewEntry(
+ var ABSContainerContent: Record "ABS Container Content"; NameFromXml: Text; OuterXml: Text; ChildNodes: XmlNodeList; var EntryNo: Integer; ResourceType: Enum "ABS Blob Resource Type")
var
OutStream: OutStream;
begin
- AddParentEntries(NameFromXml, ABSContainerContent, EntryNo);
+ if ResourceType = ResourceType::Directory then
+ ParentEntryFullNameList.Add(NameFromXml)
+ else
+ AddParentEntries(NameFromXml, ABSContainerContent, EntryNo);
ABSContainerContent.Init();
ABSContainerContent.Level := GetLevel(NameFromXml);
@@ -199,6 +203,22 @@ codeunit 9054 "ABS Container Content Helper"
Node := Document.AsXmlNode();
end;
+ local procedure GetBlobResourceType(BlobXmlNode: XmlNode): Enum "ABS Blob Resource Type"
+ var
+ ResourceTypeNode: XmlNode;
+ begin
+ if not BlobXmlNode.SelectSingleNode('.//ResourceType', ResourceTypeNode) then
+ exit(Enum::"ABS Blob Resource Type"::File);
+
+ case ResourceTypeNode.AsXmlElement().InnerText().ToLower() of
+ Format(Enum::"ABS Blob Resource Type"::File).ToLower():
+ exit(Enum::"ABS Blob Resource Type"::File);
+
+ Format(Enum::"ABS Blob Resource Type"::Directory).ToLower():
+ exit(Enum::"ABS Blob Resource Type"::Directory)
+ end;
+ end;
+
var
ParentEntryFullNameList: List of [Text];
}
\ No newline at end of file