Skip to content

Commit

Permalink
Trailing Dot (#32928)
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmcc-msft authored Feb 1, 2023
1 parent 45d2359 commit f226446
Show file tree
Hide file tree
Showing 91 changed files with 17,037 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public ShareClient(System.Uri shareUri, Azure.Storage.StorageSharedKeyCredential
public partial class ShareClientOptions : Azure.Core.ClientOptions
{
public ShareClientOptions(Azure.Storage.Files.Shares.ShareClientOptions.ServiceVersion version = Azure.Storage.Files.Shares.ShareClientOptions.ServiceVersion.V2021_12_02) { }
public bool? AllowTrailingDot { get { throw null; } set { } }
public bool? SourceAllowTrailingDot { get { throw null; } set { } }
public Azure.Storage.TransferValidationOptions TransferValidation { get { throw null; } }
public Azure.Storage.Files.Shares.ShareClientOptions.ServiceVersion Version { get { throw null; } }
public enum ServiceVersion
Expand Down
22 changes: 22 additions & 0 deletions sdk/storage/Azure.Storage.Files.Shares/src/ShareClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,28 @@ public enum ServiceVersion
/// </summary>
public TransferValidationOptions TransferValidation { get; } = new();

/// <summary>
/// If set to true, trailing dot (.) will be allowed to suffex directory and file names.
/// If false, the trailing dot will be trimmed.
/// Supported by x-ms-version 2022-11-02 and above.
/// </summary>
public bool? AllowTrailingDot { get; set; }

/// <summary>
/// If set to true, trailing dot (.) will be allowed to source file names.
/// If false, the trailing dot will be trimmed.
/// Supported by x-ms-version 2022-11-02 and above.
/// Applicable to <see cref="ShareFileClient.Rename(string, Models.ShareFileRenameOptions, System.Threading.CancellationToken)"/>,
/// <see cref="ShareFileClient.RenameAsync(string, Models.ShareFileRenameOptions, System.Threading.CancellationToken)"/>,
/// <see cref="ShareFileClient.UploadRangeFromUri(System.Uri, HttpRange, HttpRange, Models.ShareFileUploadRangeFromUriOptions, System.Threading.CancellationToken)"/>,
/// <see cref="ShareFileClient.UploadRangeFromUriAsync(System.Uri, HttpRange, HttpRange, Models.ShareFileUploadRangeFromUriOptions, System.Threading.CancellationToken)"/>,
/// <see cref="ShareFileClient.StartCopy(System.Uri, Models.ShareFileCopyOptions, System.Threading.CancellationToken)"/>,
/// <see cref="ShareFileClient.StartCopyAsync(System.Uri, Models.ShareFileCopyOptions, System.Threading.CancellationToken)"/>,
/// <see cref="ShareDirectoryClient.Rename(string, Models.ShareFileRenameOptions, System.Threading.CancellationToken)"/>,
/// and <see cref="ShareDirectoryClient.RenameAsync(string, Models.ShareFileRenameOptions, System.Threading.CancellationToken)"/>.
/// </summary>
public bool? SourceAllowTrailingDot { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="ShareClientOptions"/>
/// class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,11 +469,13 @@ internal ShareDirectoryClient(
private DirectoryRestClient BuildDirectoryRestClient(Uri uri)
{
return new DirectoryRestClient(
_clientConfiguration.ClientDiagnostics,
_clientConfiguration.Pipeline,
uri.AbsoluteUri,
clientDiagnostics: _clientConfiguration.ClientDiagnostics,
pipeline: _clientConfiguration.Pipeline,
url: uri.AbsoluteUri,
version: _clientConfiguration.ClientOptions.Version.ToVersionString(),
fileRequestIntent: _clientConfiguration.FileRequestIntent);
fileRequestIntent: _clientConfiguration.FileRequestIntent,
allowTrailingDot: _clientConfiguration.ClientOptions.AllowTrailingDot,
allowSourceTrailingDot: _clientConfiguration.ClientOptions.SourceAllowTrailingDot);
}
#endregion ctors

Expand Down
10 changes: 6 additions & 4 deletions sdk/storage/Azure.Storage.Files.Shares/src/ShareFileClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,11 +475,13 @@ internal ShareFileClient(
private FileRestClient BuildFileRestClient(Uri uri)
{
return new FileRestClient(
_clientConfiguration.ClientDiagnostics,
_clientConfiguration.Pipeline,
uri.AbsoluteUri,
clientDiagnostics: _clientConfiguration.ClientDiagnostics,
pipeline: _clientConfiguration.Pipeline,
url: uri.AbsoluteUri,
version: _clientConfiguration.ClientOptions.Version.ToVersionString(),
fileRequestIntent: _clientConfiguration.FileRequestIntent);
fileRequestIntent: _clientConfiguration.FileRequestIntent,
allowTrailingDot: _clientConfiguration.ClientOptions.AllowTrailingDot,
allowSourceTrailingDot: _clientConfiguration.ClientOptions.SourceAllowTrailingDot);
}
#endregion ctors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ public static async Task<DisposingShare> GetTestShareAsync(
this ShareClientBuilder clientBuilder,
ShareServiceClient service = default,
string shareName = default,
IDictionary<string, string> metadata = default)
IDictionary<string, string> metadata = default,
ShareClientOptions options = default)
{
service ??= clientBuilder.GetServiceClient_SharedKey();
service ??= clientBuilder.GetServiceClient_SharedKey(options);
metadata ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
shareName ??= clientBuilder.GetNewShareName();
ShareClient share = clientBuilder.AzureCoreRecordedTestBase.InstrumentClient(service.GetShareClient(shareName));
Expand All @@ -76,9 +77,10 @@ public static async Task<DisposingDirectory> GetTestDirectoryAsync(
this ShareClientBuilder clientBuilder,
ShareServiceClient service = default,
string shareName = default,
string directoryName = default)
string directoryName = default,
ShareClientOptions options = default)
{
DisposingShare test = await clientBuilder.GetTestShareAsync(service, shareName);
DisposingShare test = await clientBuilder.GetTestShareAsync(service, shareName, options: options);
directoryName ??= clientBuilder.GetNewDirectoryName();

ShareDirectoryClient directory = clientBuilder.AzureCoreRecordedTestBase.InstrumentClient(test.Share.GetDirectoryClient(directoryName));
Expand All @@ -90,9 +92,10 @@ public static async Task<DisposingFile> GetTestFileAsync(
ShareServiceClient service = default,
string shareName = default,
string directoryName = default,
string fileName = default)
string fileName = default,
ShareClientOptions options = default)
{
DisposingDirectory test = await clientBuilder.GetTestDirectoryAsync(service, shareName, directoryName);
DisposingDirectory test = await clientBuilder.GetTestDirectoryAsync(service, shareName, directoryName, options);
fileName ??= clientBuilder.GetNewFileName();
ShareFileClient file = clientBuilder.AzureCoreRecordedTestBase.InstrumentClient(test.Directory.GetFileClient(fileName));
return await DisposingFile.CreateAsync(test, file);
Expand Down
197 changes: 197 additions & 0 deletions sdk/storage/Azure.Storage.Files.Shares/tests/DirectoryClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Azure.Storage.Sas;
using Azure.Storage.Test;
using Azure.Storage.Tests.Shared;
using Microsoft.Diagnostics.Symbols;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
Expand Down Expand Up @@ -338,6 +339,45 @@ public async Task CreateAsync_Metadata()
AssertDictionaryEquality(metadata, response.Value.Metadata);
}

[RecordedTest]
[TestCase(null)]
[TestCase(false)]
[TestCase(true)]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task CreateAsync_TrailingDot(bool? allowTrailingDot)
{
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = allowTrailingDot;
await using DisposingShare test = await GetTestShareAsync(options: options);
ShareClient share = test.Share;

// Arrange
ShareDirectoryClient rootDirectory = InstrumentClient(share.GetRootDirectoryClient());
string directoryName = GetNewDirectoryName();
string directoryNameWithDot = directoryName + ".";
ShareDirectoryClient directory = InstrumentClient(share.GetDirectoryClient(directoryNameWithDot));

// Act
await directory.CreateAsync();

// Assert
List<ShareFileItem> shareFileItems = new List<ShareFileItem>();
await foreach (ShareFileItem item in rootDirectory.GetFilesAndDirectoriesAsync())
{
shareFileItems.Add(item);
}
Assert.AreEqual(1, shareFileItems.Count);

if (allowTrailingDot == true)
{
Assert.AreEqual(directoryNameWithDot, shareFileItems[0].Name);
}
else
{
Assert.AreEqual(directoryName, shareFileItems[0].Name);
}
}

[RecordedTest]
public async Task CreateIfNotExists_NotExists()
{
Expand Down Expand Up @@ -607,6 +647,19 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
e => Assert.AreEqual("ResourceNotFound", e.ErrorCode));
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task DeleteAsync_TrailingDot()
{
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingDirectory test = await SharesClientBuilder.GetTestDirectoryAsync(options: options);
ShareDirectoryClient directory = test.Directory;

// Act
await directory.DeleteAsync();
}

[RecordedTest]
public async Task GetPropertiesAsync()
{
Expand Down Expand Up @@ -700,6 +753,28 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
e => Assert.AreEqual(ShareErrorCode.ShareNotFound.ToString(), e.ErrorCode));
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task GetPropertiesAsync_TrailingDot()
{
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingShare test = await GetTestShareAsync(options: options);
ShareClient share = test.Share;

// Arrange
ShareDirectoryClient directory = InstrumentClient(share.GetDirectoryClient(GetNewDirectoryName()));

// Act
Response<ShareDirectoryInfo> createResponse = await directory.CreateIfNotExistsAsync();
Response<ShareDirectoryProperties> getPropertiesResponse = await directory.GetPropertiesAsync();

// Assert
Assert.AreEqual(createResponse.Value.ETag, getPropertiesResponse.Value.ETag);
Assert.AreEqual(createResponse.Value.LastModified, getPropertiesResponse.Value.LastModified);
AssertPropertiesEqual(createResponse.Value.SmbProperties, getPropertiesResponse.Value.SmbProperties);
}

[RecordedTest]
public async Task SetHttpHeadersAsync()
{
Expand Down Expand Up @@ -855,6 +930,23 @@ await TestHelper.AssertExpectedExceptionAsync<ArgumentException>(
e => Assert.AreEqual("filePermission and filePermissionKey cannot both be set", e.Message));
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task SetHttpHeadersAsync_TrailingDot()
{
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingShare test = await GetTestShareAsync(options: options);
ShareClient share = test.Share;

// Arrange
ShareDirectoryClient directory = InstrumentClient(share.GetDirectoryClient(GetNewDirectoryName()));
await directory.CreateIfNotExistsAsync();

// Act
await directory.SetHttpHeadersAsync();
}

[RecordedTest]
public async Task SetMetadataAsync()
{
Expand Down Expand Up @@ -914,6 +1006,27 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
e => Assert.AreEqual("ResourceNotFound", e.ErrorCode));
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task SetMetadataAsync_TrailingDot()
{
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingDirectory test = await SharesClientBuilder.GetTestDirectoryAsync(options: options);
ShareDirectoryClient directory = test.Directory;

// Arrange
IDictionary<string, string> metadata = BuildMetadata();

// Act
Response<ShareDirectoryInfo> setMetadataResponse = await directory.SetMetadataAsync(metadata);
Assert.AreNotEqual(DateTimeOffset.MinValue, setMetadataResponse.Value.LastModified);

// Assert
Response<ShareDirectoryProperties> response = await directory.GetPropertiesAsync();
AssertDictionaryEquality(metadata, response.Value.Metadata);
}

[RecordedTest]
public async Task ListFilesAndDirectoriesSegmentAsync()
{
Expand Down Expand Up @@ -1171,6 +1284,25 @@ public async Task ListFilesAndDirectories_Encoded_Prefix()
Assert.AreEqual(specialCharDirectoryName, shareFileItems[0].Name);
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task ListFilesAndDirectories_TrailingDot()
{
// Arrange
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingShare test = await GetTestShareAsync(options: options);
ShareDirectoryClient directoryClient = await test.Share.CreateDirectoryAsync(GetNewDirectoryName() + ".");

// Act
List<ShareFileItem> shareFileItems = new List<ShareFileItem>();

await foreach (ShareFileItem item in directoryClient.GetFilesAndDirectoriesAsync())
{
shareFileItems.Add(item);
}
}

[RecordedTest]
[AsyncOnly]
public async Task ListHandles()
Expand Down Expand Up @@ -1240,6 +1372,23 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
actualException => Assert.AreEqual("ResourceNotFound", actualException.ErrorCode));
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task ListHandles_TrailingDot()
{
// Arrange
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingDirectory test = await SharesClientBuilder.GetTestDirectoryAsync(options: options);
ShareDirectoryClient directory = test.Directory;

// Act
IList<ShareFileHandle> handles = await directory.GetHandlesAsync().ToListAsync();

// Assert
Assert.AreEqual(0, handles.Count);
}

[RecordedTest]
public async Task ForceCloseHandles_Min()
{
Expand Down Expand Up @@ -1322,6 +1471,24 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
actualException => Assert.AreEqual("InvalidHeaderValue", actualException.ErrorCode));
}

[RecordedTest]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task ForceCloseHandles_TrailingDot()
{
// Arrange
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
await using DisposingDirectory test = await SharesClientBuilder.GetTestDirectoryAsync(options: options);
ShareDirectoryClient directory = test.Directory;

// Act
CloseHandlesResult response = await directory.ForceCloseAllHandlesAsync();

// Assert
Assert.AreEqual(0, response.ClosedHandlesCount);
Assert.AreEqual(0, response.FailedHandlesCount);
}

[RecordedTest]
public async Task CreateSubdirectoryAsync()
{
Expand Down Expand Up @@ -2070,6 +2237,36 @@ public async Task RenameAsync_SourceSasCredentialDestSasUri()
Response<ShareDirectoryProperties> response = await destDirectory.GetPropertiesAsync();
}

[RecordedTest]
[TestCase(null)]
[TestCase(false)]
[TestCase(true)]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2022_11_02)]
public async Task RenameAsync_TrailingDot(bool? sourceAllowTrailingDot)
{
// Arrange
ShareClientOptions options = GetOptions();
options.AllowTrailingDot = true;
options.SourceAllowTrailingDot = sourceAllowTrailingDot;
await using DisposingShare test = await SharesClientBuilder.GetTestShareAsync(options: options);
string destDirectoryName = GetNewDirectoryName() + ".";
ShareDirectoryClient sourceDirectory = InstrumentClient(test.Share.GetDirectoryClient(GetNewDirectoryName() + "."));
await sourceDirectory.CreateAsync();

// Act
if (sourceAllowTrailingDot == true)
{
ShareDirectoryClient destDirectory = await sourceDirectory.RenameAsync(destDirectoryName);
}
else
{
// Act
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
sourceDirectory.RenameAsync(destDirectoryName),
e => Assert.AreEqual(e.ErrorCode, "ResourceNotFound"));
}
}

#region GenerateSasTests
[RecordedTest]
public void CanGenerateSas_ClientConstructors()
Expand Down
Loading

0 comments on commit f226446

Please sign in to comment.