mirror of https://github.com/abpframework/abp.git
15 changed files with 621 additions and 0 deletions
@ -0,0 +1,24 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> |
|||
<Nullable>enable</Nullable> |
|||
<WarningsAsErrors>Nullable</WarningsAsErrors> |
|||
<AssemblyName>Volo.Abp.BlobStoring.Google</AssemblyName> |
|||
<PackageId>Volo.Abp.BlobStoring.Google</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" /> |
|||
<PackageReference Include="Google.Cloud.Storage.V1" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
[DependsOn(typeof(AbpBlobStoringModule))] |
|||
public class AbpBlobStoringGoogleModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public class DefaultGoogleBlobNameCalculator: IGoogleBlobNameCalculator, ITransientDependency |
|||
{ |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
|
|||
public DefaultGoogleBlobNameCalculator(ICurrentTenant currentTenant) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
} |
|||
|
|||
public virtual string Calculate(BlobProviderArgs args) |
|||
{ |
|||
return CurrentTenant.Id == null |
|||
? $"host/{args.BlobName}" |
|||
: $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public static class GoogleBlobContainerConfigurationExtensions |
|||
{ |
|||
public static GoogleBlobProviderConfiguration GetGoogleConfiguration( |
|||
this BlobContainerConfiguration containerConfiguration) |
|||
{ |
|||
return new GoogleBlobProviderConfiguration(containerConfiguration); |
|||
} |
|||
|
|||
public static BlobContainerConfiguration UseGoogle( |
|||
this BlobContainerConfiguration containerConfiguration, |
|||
Action<GoogleBlobProviderConfiguration> azureConfigureAction) |
|||
{ |
|||
containerConfiguration.ProviderType = typeof(GoogleBlobProvider); |
|||
containerConfiguration.NamingNormalizers.TryAdd<GoogleBlobNamingNormalizer>(); |
|||
|
|||
azureConfigureAction(new GoogleBlobProviderConfiguration(containerConfiguration)); |
|||
|
|||
return containerConfiguration; |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
using System.Globalization; |
|||
using System.Text.RegularExpressions; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public class GoogleBlobNamingNormalizer: IBlobNamingNormalizer, ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
/// https://cloud.google.com/storage/docs/buckets#naming
|
|||
/// </summary>
|
|||
public string NormalizeContainerName(string containerName) |
|||
{ |
|||
using (CultureHelper.Use(CultureInfo.InvariantCulture)) |
|||
{ |
|||
// All letters in a Bucket name must be lowercase.
|
|||
containerName = containerName.ToLower(); |
|||
|
|||
// Bucket names must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters.
|
|||
if(containerName.Contains(".")) |
|||
{ |
|||
if (containerName.Length > 222) |
|||
{ |
|||
containerName = containerName.Substring(0, 222); |
|||
} |
|||
|
|||
var parts = containerName.Split('.'); |
|||
for (var i = 0; i < parts.Length; i++) |
|||
{ |
|||
if (parts[i].Length > 63) |
|||
{ |
|||
parts[i] = parts[i].Substring(0, 63); |
|||
} |
|||
} |
|||
|
|||
containerName = string.Join(".", parts); |
|||
} |
|||
else if (containerName.Length > 63) |
|||
{ |
|||
containerName = containerName.Substring(0, 63); |
|||
} |
|||
|
|||
//Bucket names can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Spaces are not allowed. Names containing dots require verification.
|
|||
containerName = Regex.Replace(containerName, "[^a-z0-9-_.]", string.Empty); |
|||
|
|||
//Be a syntactically valid DNS name (for example, bucket..example.com is not valid because it contains two dots in a row).
|
|||
containerName = Regex.Replace(containerName, "[.]{2,}", "."); |
|||
|
|||
//Bucket names cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4).
|
|||
containerName = Regex.Replace(containerName, "^(?:(?:^|\\.)(?:2(?:5[0-5]|[0-4]\\d)|1?\\d?\\d)){4}$", string.Empty); |
|||
|
|||
//Bucket names cannot begin with the "goog" prefix.
|
|||
containerName = Regex.Replace(containerName, "^goog", string.Empty); |
|||
|
|||
//Bucket names cannot contain "google" or close misspellings, such as "g00gle".
|
|||
containerName = Regex.Replace(containerName, "google", string.Empty); |
|||
|
|||
//Bucket names must start and end with a number or letter.
|
|||
containerName = RemoveInvalidStartEndCharacters(containerName); |
|||
|
|||
// Bucket names must be from 3 through 63 characters long. Names containing dots can contain up to 222 characters.
|
|||
if (containerName.Length < 3) |
|||
{ |
|||
var length = containerName.Length; |
|||
for (var i = 0; i < 3 - length; i++) |
|||
{ |
|||
containerName += "0"; |
|||
} |
|||
} |
|||
|
|||
return containerName; |
|||
} |
|||
} |
|||
|
|||
protected virtual string RemoveInvalidStartEndCharacters(string containerName) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(containerName)) |
|||
{ |
|||
return containerName; |
|||
} |
|||
|
|||
if (!char.IsLetterOrDigit(containerName[0])) |
|||
{ |
|||
containerName = containerName.Substring(1); |
|||
return RemoveInvalidStartEndCharacters( containerName); |
|||
} |
|||
|
|||
if (!char.IsLetterOrDigit(containerName[containerName.Length - 1])) |
|||
{ |
|||
containerName = containerName.Substring(0, containerName.Length - 1); |
|||
return RemoveInvalidStartEndCharacters(containerName); |
|||
} |
|||
|
|||
return containerName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// https://cloud.google.com/storage/docs/objects#naming
|
|||
/// </summary>
|
|||
public string NormalizeBlobName(string blobName) |
|||
{ |
|||
//Object names can contain any sequence of valid Unicode characters, of length 1-1024 bytes when UTF-8 encoded
|
|||
if (blobName.Length > 1024) |
|||
{ |
|||
blobName = blobName.Substring(0, 1024); |
|||
} |
|||
|
|||
//Object names cannot contain Carriage Return or Line Feed characters.
|
|||
blobName = Regex.Replace(blobName, "[\r\n]", string.Empty); |
|||
|
|||
//Object names cannot start with .well-known/acme-challenge/.
|
|||
blobName = Regex.Replace(blobName, "^\\.well-known/acme-challenge/", string.Empty); |
|||
|
|||
//Objects cannot be named . or ...
|
|||
blobName = Regex.Replace(blobName, "^\\.\\.?$", string.Empty); |
|||
|
|||
return blobName; |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Google; |
|||
using Google.Apis.Auth.OAuth2; |
|||
using Google.Cloud.Storage.V1; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public class GoogleBlobProvider : BlobProviderBase, ITransientDependency |
|||
{ |
|||
protected IGoogleBlobNameCalculator GoogleBlobNameCalculator { get; } |
|||
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } |
|||
|
|||
public GoogleBlobProvider(IGoogleBlobNameCalculator googleBlobNameCalculator, IBlobNormalizeNamingService blobNormalizeNamingService) |
|||
{ |
|||
GoogleBlobNameCalculator = googleBlobNameCalculator; |
|||
BlobNormalizeNamingService = blobNormalizeNamingService; |
|||
} |
|||
|
|||
public async override Task SaveAsync(BlobProviderSaveArgs args) |
|||
{ |
|||
var configuration = args.Configuration.GetGoogleConfiguration(); |
|||
var storageClient = await GetStorageClientClientAsync(args); |
|||
var blobName = GoogleBlobNameCalculator.Calculate(args); |
|||
var containerName = GetContainerName(args); |
|||
|
|||
if(await BlobExistsAsync(args, blobName) && !args.OverrideExisting) |
|||
{ |
|||
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{GetContainerName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); |
|||
} |
|||
|
|||
if (configuration.CreateContainerIfNotExists) |
|||
{ |
|||
await CreateContainerIfNotExists(args); |
|||
} |
|||
|
|||
await storageClient.UploadObjectAsync(containerName, blobName, contentType: "application/octet-stream", args.BlobStream, |
|||
new UploadObjectOptions |
|||
{ |
|||
IfGenerationMatch = !args.OverrideExisting ? 0 : 1 |
|||
}); |
|||
} |
|||
|
|||
public async override Task<bool> DeleteAsync(BlobProviderDeleteArgs args) |
|||
{ |
|||
var storageClient = await GetStorageClientClientAsync(args); |
|||
var blobName = GoogleBlobNameCalculator.Calculate(args); |
|||
var containerName = GetContainerName(args); |
|||
|
|||
await storageClient.DeleteObjectAsync(containerName, blobName); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public async override Task<bool> ExistsAsync(BlobProviderExistsArgs args) |
|||
{ |
|||
var blobName = GoogleBlobNameCalculator.Calculate(args); |
|||
return await BlobExistsAsync(args, blobName); |
|||
} |
|||
|
|||
public async override Task<Stream?> GetOrNullAsync(BlobProviderGetArgs args) |
|||
{ |
|||
var storageClient = await GetStorageClientClientAsync(args); |
|||
var blobName = GoogleBlobNameCalculator.Calculate(args); |
|||
var containerName = GetContainerName(args); |
|||
|
|||
var @object = await storageClient.GetObjectAsync(containerName, blobName); |
|||
if (@object == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var stream = new MemoryStream(); |
|||
|
|||
await storageClient.DownloadObjectAsync(containerName, blobName, stream); |
|||
|
|||
stream.Seek(0, SeekOrigin.Begin); |
|||
|
|||
return stream; |
|||
} |
|||
|
|||
protected virtual string GetContainerName(BlobProviderArgs args) |
|||
{ |
|||
var configuration = args.Configuration.GetGoogleConfiguration(); |
|||
return configuration.ContainerName.IsNullOrWhiteSpace() |
|||
? args.ContainerName |
|||
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName!); |
|||
} |
|||
|
|||
protected virtual async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName) |
|||
{ |
|||
var @object = await (await GetStorageClientClientAsync(args)).GetObjectAsync(args.ContainerName, blobName); |
|||
|
|||
return @object != null; |
|||
} |
|||
|
|||
protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args) |
|||
{ |
|||
var storageClient = await GetStorageClientClientAsync(args); |
|||
var configuration = args.Configuration.GetGoogleConfiguration(); |
|||
try |
|||
{ |
|||
await storageClient.GetBucketAsync(args.ContainerName); |
|||
} |
|||
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
|||
{ |
|||
await storageClient.CreateBucketAsync(configuration.ProjectId, GetContainerName(args)); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<StorageClient> GetStorageClientClientAsync(BlobProviderArgs args) |
|||
{ |
|||
var configuration = args.Configuration.GetGoogleConfiguration(); |
|||
var googleCredential = GoogleCredential.FromServiceAccountCredential( |
|||
new ServiceAccountCredential( |
|||
new ServiceAccountCredential.Initializer(configuration.ClientEmail) |
|||
{ |
|||
ProjectId = configuration.ProjectId, |
|||
Scopes = configuration.Scopes |
|||
} |
|||
.FromPrivateKey(configuration.PrivateKey) |
|||
)); |
|||
|
|||
return await StorageClient.CreateAsync(googleCredential); |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public class GoogleBlobProviderConfiguration |
|||
{ |
|||
private readonly BlobContainerConfiguration _containerConfiguration; |
|||
|
|||
public GoogleBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) |
|||
{ |
|||
_containerConfiguration = containerConfiguration; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unique identifier for your project.
|
|||
/// For more info see: https://cloud.google.com/resource-manager/docs/creating-managing-projects
|
|||
/// </summary>
|
|||
public string? ProjectId { |
|||
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ProjectId); |
|||
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ProjectId, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Email address that generated by the Google Cloud.
|
|||
/// </summary>
|
|||
public string? ClientEmail { |
|||
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ClientEmail); |
|||
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ClientEmail, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Private key that generated by Google Cloud.
|
|||
/// Starts with '-----BEGIN PRIVATE KEY-----'
|
|||
/// and ends with '-----END PRIVATE KEY-----'
|
|||
/// </summary>
|
|||
public string? PrivateKey { |
|||
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.PrivateKey); |
|||
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.PrivateKey, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Available OAuth 2.0 scopes.
|
|||
/// </summary>
|
|||
public List<string>? Scopes { |
|||
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.Scopes, new List<string>()); |
|||
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.Scopes, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The name can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Spaces are not allowed. Names containing dots require verification.
|
|||
/// Must start and end with a number or letter.
|
|||
/// Must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters.
|
|||
/// Cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4).
|
|||
/// Cannot begin with the "goog" prefix.
|
|||
/// Cannot contain "google" or close misspellings, such as "g00gle".
|
|||
/// If this parameter is not specified, the ContainerName of the <see cref="BlobProviderArgs"/> will be used.
|
|||
/// </summary>
|
|||
public string? ContainerName { |
|||
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ContainerName); |
|||
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ContainerName, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Default value: false.
|
|||
/// </summary>
|
|||
public bool CreateContainerIfNotExists { |
|||
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.CreateContainerIfNotExists, false); |
|||
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.CreateContainerIfNotExists, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public static class GoogleBlobProviderConfigurationNames |
|||
{ |
|||
public const string ProjectId = "Google.ProjectId"; |
|||
public const string ClientEmail = "Google.ClientEmail"; |
|||
public const string PrivateKey = "Google.PrivateKey"; |
|||
public const string Scopes = "Google.Scopes"; |
|||
public const string ContainerName = "Google.ContainerName"; |
|||
public const string CreateContainerIfNotExists = "Google.CreateContainerIfNotExists"; |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public interface IGoogleBlobNameCalculator |
|||
{ |
|||
string Calculate(BlobProviderArgs args); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<UserSecretsId>9f0d2c00-80c1-435b-bfab-2c39c8249091</UserSecretsId> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
|||
<ProjectReference Include="..\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,19 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public class AbpBlobStoringGoogleTestCommonBase : AbpIntegratedTest<AbpBlobStoringGoogleTestCommonModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
|
|||
public class AbpBlobStoringGoogleTestBase : AbpIntegratedTest<AbpBlobStoringGoogleTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
|
|||
/// <summary>
|
|||
/// This module will not try to connect to azure.
|
|||
/// </summary>
|
|||
[DependsOn( |
|||
typeof(AbpBlobStoringGoogleModule), |
|||
typeof(AbpBlobStoringTestModule) |
|||
)] |
|||
public class AbpBlobStoringGoogleTestCommonModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
|
|||
[DependsOn( |
|||
typeof(AbpBlobStoringGoogleTestCommonModule) |
|||
)] |
|||
public class AbpBlobStoringGoogleTestModule : AbpModule |
|||
{ |
|||
private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; |
|||
|
|||
private string _connectionString; |
|||
|
|||
private readonly string _randomContainerName = "abp-azure-test-container-" + Guid.NewGuid().ToString("N"); |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => |
|||
{ |
|||
builder.AddUserSecrets(UserSecretsId); |
|||
})); |
|||
|
|||
// var configuration = context.Services.GetConfiguration();
|
|||
// _connectionString = configuration["Azure:ConnectionString"];
|
|||
//
|
|||
// Configure<AbpBlobStoringOptions>(options =>
|
|||
// {
|
|||
// options.Containers.ConfigureAll((containerName, containerConfiguration) =>
|
|||
// {
|
|||
// containerConfiguration.UseAzure(azure =>
|
|||
// {
|
|||
// azure.ConnectionString = _connectionString;
|
|||
// azure.ContainerName = _randomContainerName;
|
|||
// azure.CreateContainerIfNotExists = true;
|
|||
// });
|
|||
// });
|
|||
// });
|
|||
} |
|||
|
|||
public override void OnApplicationShutdown(ApplicationShutdownContext context) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.BlobStoring.Google; |
|||
|
|||
public class DefaultGoogleBlobNamingNormalizerProvider_Tests: AbpBlobStoringGoogleTestCommonBase |
|||
{ |
|||
private readonly IBlobNamingNormalizer _blobNamingNormalizer; |
|||
|
|||
public DefaultGoogleBlobNamingNormalizerProvider_Tests() |
|||
{ |
|||
_blobNamingNormalizer = GetRequiredService<IBlobNamingNormalizer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Lowercase() |
|||
{ |
|||
var filename = "ThisIsMyContainerName"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("thisismycontainername"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Only_Letters_Numbers_Dash_Dots_Underscores() |
|||
{ |
|||
var filename = ",./this-i,/s-my-c,/ont,/ai+*/=!@#$n^&*er.name+/"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("this-is-my-container.name"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Only_Start_With_Letters_Or_Numbers() |
|||
{ |
|||
var filename = "-this.--is-.-.-my--_container---name-"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("this.--is-.-.-my--_container---name"); |
|||
|
|||
filename = ".this.--is-.-.-my--container---name0._"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("this.--is-.-.-my--container---name0"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Min_Length() |
|||
{ |
|||
var filename = "a"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.Length.ShouldBeGreaterThanOrEqualTo(3); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Max_Length() |
|||
{ |
|||
var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.Length.ShouldBeLessThanOrEqualTo(63); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Must_Not_Be_Ip_Address() |
|||
{ |
|||
var filename = "192.168.5.4"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("000"); |
|||
|
|||
filename = "a.192.168.5.4"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("a.192.168.5.4"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Dots() |
|||
{ |
|||
var filename = ".this..is.my.container....name."; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("this.is.my.container.name"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_DNS() |
|||
{ |
|||
var filename = "bucket...example..com"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("bucket.example.com"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NormalizeContainerName_Max_Length_Dash() |
|||
{ |
|||
var filename = "-this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b-p-"; |
|||
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
|||
filename.ShouldBe("this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b"); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue