Browse Source

Merge pull request #20747 from abpframework/Googleblob

Add Google Cloud Storage BLOB Provider
pull/20754/head
Engincan VESKE 2 years ago
committed by GitHub
parent
commit
1a5fc718d0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      Directory.Packages.props
  2. 14
      framework/Volo.Abp.sln
  3. 24
      framework/src/Volo.Abp.BlobStoring.Google/Volo.Abp.BlobStoring.Google.csproj
  4. 9
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/AbpBlobStoringGoogleModule.cs
  5. 21
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/DefaultGoogleBlobNameCalculator.cs
  6. 24
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobContainerConfigurationExtensions.cs
  7. 120
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobNamingNormalizer.cs
  8. 156
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobProvider.cs
  9. 70
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobProviderConfiguration.cs
  10. 11
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobProviderConfigurationNames.cs
  11. 6
      framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/IGoogleBlobNameCalculator.cs
  12. 19
      framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo.Abp.BlobStoring.Google.Tests.csproj
  13. 19
      framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/AbpBlobStoringGoogleTestBase.cs
  14. 90
      framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/AbpBlobStoringGoogleTestModule.cs
  15. 94
      framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/DefaultGoogleBlobNamingNormalizerProvider_Tests.cs
  16. 13
      framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/GoogleBlobContainer_Tests.cs
  17. 56
      framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/GoogleBlobNameCalculator_Tests.cs

1
Directory.Packages.props

@ -38,6 +38,7 @@
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<PackageVersion Include="FluentValidation" Version="11.8.0" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageVersion Include="HtmlSanitizer" Version="8.0.746" />

14
framework/Volo.Abp.sln

@ -467,6 +467,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.RemoteServices.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Abstractions", "src\Volo.Abp.AspNetCore.Abstractions\Volo.Abp.AspNetCore.Abstractions.csproj", "{E1051CD0-9262-4869-832D-B951723F4DDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google", "src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj", "{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google.Tests", "test\Volo.Abp.BlobStoring.Google.Tests\Volo.Abp.BlobStoring.Google.Tests.csproj", "{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1393,6 +1397,14 @@ Global
{E1051CD0-9262-4869-832D-B951723F4DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1051CD0-9262-4869-832D-B951723F4DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1051CD0-9262-4869-832D-B951723F4DDE}.Release|Any CPU.Build.0 = Release|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Release|Any CPU.Build.0 = Release|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1628,6 +1640,8 @@ Global
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{DACD4485-61BE-4DE5-ACAE-4FFABC122500} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{E1051CD0-9262-4869-832D-B951723F4DDE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

24
framework/src/Volo.Abp.BlobStoring.Google/Volo.Abp.BlobStoring.Google.csproj

@ -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>

9
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/AbpBlobStoringGoogleModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Google;
[DependsOn(typeof(AbpBlobStoringModule))]
public class AbpBlobStoringGoogleModule : AbpModule
{
}

21
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/DefaultGoogleBlobNameCalculator.cs

@ -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}";
}
}

24
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobContainerConfigurationExtensions.cs

@ -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> googleConfigureAction)
{
containerConfiguration.ProviderType = typeof(GoogleBlobProvider);
containerConfiguration.NamingNormalizers.TryAdd<GoogleBlobNamingNormalizer>();
googleConfigureAction(new GoogleBlobProviderConfiguration(containerConfiguration));
return containerConfiguration;
}
}

120
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobNamingNormalizer.cs

@ -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;
}
}

156
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobProvider.cs

@ -0,0 +1,156 @@
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);
}
public async override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var storageClient = await GetStorageClientClientAsync(args);
var blobName = GoogleBlobNameCalculator.Calculate(args);
var containerName = GetContainerName(args);
try
{
await storageClient.DeleteObjectAsync(containerName, blobName);
}
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound)
{
return true;
}
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);
if(!await BlobExistsAsync(args, blobName))
{
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 storageClient = await GetStorageClientClientAsync(args);
if(!await ContainerExistsAsync(args, storageClient))
{
return false;
}
try
{
await storageClient.GetObjectAsync(GetContainerName(args), blobName);
}
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound)
{
return false;
}
return true;
}
protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args)
{
var storageClient = await GetStorageClientClientAsync(args);
var configuration = args.Configuration.GetGoogleConfiguration();
if(await ContainerExistsAsync(args, storageClient))
{
return;
}
await storageClient.CreateBucketAsync(configuration.ProjectId, GetContainerName(args));
}
protected virtual async Task<bool> ContainerExistsAsync(BlobProviderArgs args, StorageClient client)
{
try
{
await client.GetBucketAsync(GetContainerName(args));
}
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound)
{
return false;
}
return true;
}
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);
}
}

70
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobProviderConfiguration.cs

@ -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);
}
}

11
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/GoogleBlobProviderConfigurationNames.cs

@ -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";
}

6
framework/src/Volo.Abp.BlobStoring.Google/Volo/Abp/BlobStoring/Google/IGoogleBlobNameCalculator.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.BlobStoring.Google;
public interface IGoogleBlobNameCalculator
{
string Calculate(BlobProviderArgs args);
}

19
framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo.Abp.BlobStoring.Google.Tests.csproj

@ -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>

19
framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/AbpBlobStoringGoogleTestBase.cs

@ -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();
}
}

90
framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/AbpBlobStoringGoogleTestModule.cs

@ -0,0 +1,90 @@
using System;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Cloud.Storage.V1;
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 Google Cloud Storage.
/// </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 _clientEmail;
private string _projectId;
private string _privateKey;
private readonly string _randomContainerName = "abp-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();
_clientEmail = configuration["Google:ClientEmail"];
_projectId = configuration["Google:ProjectId"];
_privateKey = configuration["Google:PrivateKey"];
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
containerConfiguration.UseGoogle(google =>
{
google.ClientEmail = _clientEmail;
google.ProjectId = _projectId = "wide-origin-296910";
google.PrivateKey = _privateKey;
google.ContainerName = _randomContainerName;
google.CreateContainerIfNotExists = true;
});
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
var googleCredential = GoogleCredential.FromServiceAccountCredential(
new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(_clientEmail)
{
ProjectId = _projectId
}
.FromPrivateKey(_privateKey)
));
var client = StorageClient.Create(googleCredential);
try
{
client.DeleteBucket(_randomContainerName, new DeleteBucketOptions
{
DeleteObjects = true
});
}
catch (Exception e)
{
// ignored
}
}
}

94
framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/DefaultGoogleBlobNamingNormalizerProvider_Tests.cs

@ -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");
}
}

13
framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/GoogleBlobContainer_Tests.cs

@ -0,0 +1,13 @@
using Xunit;
namespace Volo.Abp.BlobStoring.Google;
/*
//Please set the correct connection string in secrets.json and continue the test.
public class GoogleBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringGoogleTestModule>
{
public GoogleBlobContainer_Tests()
{
}
}
*/

56
framework/test/Volo.Abp.BlobStoring.Google.Tests/Volo/Abp/BlobStoring/Google/GoogleBlobNameCalculator_Tests.cs

@ -0,0 +1,56 @@
using System;
using Shouldly;
using Volo.Abp.MultiTenancy;
using Xunit;
namespace Volo.Abp.BlobStoring.Google;
public class GoogleBlobNameCalculator_Tests : AbpBlobStoringGoogleTestCommonBase
{
private readonly IGoogleBlobNameCalculator _calculator;
private readonly ICurrentTenant _currentTenant;
private const string GoogleContainerName = "/";
private const string GoogleSeparator = "/";
public GoogleBlobNameCalculator_Tests()
{
_calculator = GetRequiredService<IGoogleBlobNameCalculator>();
_currentTenant = GetRequiredService<ICurrentTenant>();
}
[Fact]
public void Default_Settings()
{
_calculator.Calculate(
GetArgs("my-container", "my-blob")
).ShouldBe($"host{GoogleSeparator}my-blob");
}
[Fact]
public void Default_Settings_With_TenantId()
{
var tenantId = Guid.NewGuid();
using (_currentTenant.Change(tenantId))
{
_calculator.Calculate(
GetArgs("my-container", "my-blob")
).ShouldBe($"tenants{GoogleSeparator}{tenantId:D}{GoogleSeparator}my-blob");
}
}
private static BlobProviderArgs GetArgs(
string containerName,
string blobName)
{
return new BlobProviderGetArgs(
containerName,
new BlobContainerConfiguration().UseGoogle(x =>
{
x.ContainerName = containerName;
}),
blobName
);
}
}
Loading…
Cancel
Save