Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AzureBlobStorage.WriteBlob - Initial implementation #100

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
27 changes: 27 additions & 0 deletions .github/workflows/WriteBlob_build_and_test_on_main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: WriteBlob_build_main

on:
push:
branches:
- main
paths:
- 'Frends.AzureBlobStorage.WriteBlob/**'
workflow_dispatch:

jobs:
build:
uses: FrendsPlatform/FrendsTasks/.github/workflows/build_main.yml@main
with:
workdir: Frends.AzureBlobStorage.WriteBlob
env_var_name_1: Frends_AzureBlobStorage_ConnString
env_var_name_2: Frends_AzureBlobStorage_AppID
env_var_name_3: Frends_AzureBlobStorage_ClientSecret
env_var_name_4: Frends_AzureBlobStorage_TenantID
env_var_name_5: Frends_AzureBlobStorage_SASToken
secrets:
badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }}
env_var_value_1: ${{ secrets.Frends_AZUREBLOBSTORAGE_CONNSTRING }}
env_var_value_2: ${{ secrets.Frends_AZUREBLOBSTORAGE_APPID }}
env_var_value_3: ${{ secrets.Frends_AZUREBLOBSTORAGE_CLIENTSECRET }}
env_var_value_4: ${{ secrets.Frends_AZUREBLOBSTORAGE_TENANTID }}
env_var_value_5: ${{ secrets.Frends_AZUREBLOBSTORAGE_SASTOKEN }}
28 changes: 28 additions & 0 deletions .github/workflows/WriteBlob_build_and_test_on_push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: WriteBlob_push

on:
push:
branches-ignore:
- main
paths:
- 'Frends.AzureBlobStorage.WriteBlob/**'
workflow_dispatch:

jobs:
build:
uses: FrendsPlatform/FrendsTasks/.github/workflows/build_test.yml@main
with:
workdir: Frends.AzureBlobStorage.WriteBlob
env_var_name_1: Frends_AzureBlobStorage_ConnString
env_var_name_2: Frends_AzureBlobStorage_AppID
env_var_name_3: Frends_AzureBlobStorage_ClientSecret
env_var_name_4: Frends_AzureBlobStorage_TenantID
env_var_name_5: Frends_AzureBlobStorage_SASToken
secrets:
badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }}
test_feed_api_key: ${{ secrets.TASKS_TEST_FEED_API_KEY }}
env_var_value_1: ${{ secrets.Frends_AZUREBLOBSTORAGE_CONNSTRING }}
env_var_value_2: ${{ secrets.Frends_AZUREBLOBSTORAGE_APPID }}
env_var_value_3: ${{ secrets.Frends_AZUREBLOBSTORAGE_CLIENTSECRET }}
env_var_value_4: ${{ secrets.Frends_AZUREBLOBSTORAGE_TENANTID }}
env_var_value_5: ${{ secrets.Frends_AZUREBLOBSTORAGE_SASTOKEN }}
12 changes: 12 additions & 0 deletions .github/workflows/WriteBlob_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: WriteBlob_release

on:
workflow_dispatch:

jobs:
build:
uses: FrendsPlatform/FrendsTasks/.github/workflows/release.yml@main
with:
workdir: Frends.AzureBlobStorage.WriteBlob
secrets:
feed_api_key: ${{ secrets.TASKS_FEED_API_KEY }}
5 changes: 5 additions & 0 deletions Frends.AzureBlobStorage.WriteBlob/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## [1.0.0] - 2025-01-24
### Added
- Initial implementation of Frends.AzureBlobStorage.WriteBlob.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="2.20.0" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
RikuVirtanen marked this conversation as resolved.
Show resolved Hide resolved

<ItemGroup>
<ProjectReference Include="..\Frends.AzureBlobStorage.WriteBlob\Frends.AzureBlobStorage.WriteBlob.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
using NUnit.Framework;
using Azure.Storage.Blobs;
using Frends.AzureBlobStorage.WriteBlob.Definitions;
using Frends.AzureBlobStorage.WriteBlob.Enums;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Azure.Storage.Blobs.Models;
using System.Collections.Generic;
using Azure.Identity;

namespace Frends.AzureBlobStorage.WriteBlob.Tests;

[TestFixture]
public class UnitTests
{
private readonly string _connectionString = Environment.GetEnvironmentVariable("Frends_AzureBlobStorage_ConnString");
private string _containerName;
private readonly string _appID = Environment.GetEnvironmentVariable("Frends_AzureBlobStorage_AppID");
private readonly string _clientSecret = Environment.GetEnvironmentVariable("Frends_AzureBlobStorage_ClientSecret");
private readonly string _tenantID = Environment.GetEnvironmentVariable("Frends_AzureBlobStorage_TenantID");
private readonly string _uri = "https://stataskdevelopment.blob.core.windows.net";
private readonly string _sasToken = Environment.GetEnvironmentVariable("Frends_AzureBlobStorage_SASToken");
private readonly Tag[] _tags = new[] { new Tag { Name = "TagName", Value = "TagValue" } };
private readonly string _container = "const-test-container";
private Destination _destination;
private Source _source;
private Options _options;
private readonly string _testContent = "This is test data";

[SetUp]
public async Task TestSetup()
{
_containerName = $"test-container{DateTime.Now.ToString("mmssffffff", CultureInfo.InvariantCulture)}";

await CreateBlobContainer(_connectionString, _containerName);

_source = new Source
{
SourceType = SourceType.String,
ContentString = _testContent,
ContentBytes = Encoding.UTF8.GetBytes(_testContent),
Encoding = FileEncoding.UTF8
};

_destination = new Destination
{
ConnectionMethod = ConnectionMethod.ConnectionString,
ContainerName = _containerName,
ConnectionString = _connectionString,
CreateContainerIfItDoesNotExist = false,
BlobName = $"testblob_{Guid.NewGuid()}",
Tags = null,
HandleExistingFile = HandleExistingFile.Overwrite,
TenantID = _tenantID,
ApplicationID = _appID,
Uri = _uri,
ClientSecret = _clientSecret,
};

_options = new Options() { ThrowErrorOnFailure = true };
}

[TearDown]
public async Task CleanUp()
{
await DeleteBlobContainer(_containerName);
}

[Test]
public async Task WriteBlob_TestWriteFromString()
{
// Connection string
var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);

// OAuth
_destination.BlobName = $"testblob_{Guid.NewGuid()}";
_destination.ConnectionMethod = ConnectionMethod.OAuth2;
result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));
}

[Test]
public async Task WriteBlob_TestWriteFromByteArray()
{
_source.SourceType = SourceType.Bytes;

// Connection string
var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);

// OAuth
_destination.BlobName = $"testblob_{Guid.NewGuid()}";
_destination.ConnectionMethod = ConnectionMethod.OAuth2;
result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));
}

[Test]
public async Task WriteBlob_TestFolderBlobName()
{
// Connection string
_destination.BlobName = $"C:\\folder\\testBlob_{Guid.NewGuid()}";
var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);

// OAuth
_destination.BlobName = $"C:\\folder\\testBlob_{Guid.NewGuid()}";
_destination.ConnectionMethod = ConnectionMethod.OAuth2;
result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));
}

[Test]
public async Task WriteBlob_TestEncoding()
{
var encodings = new List<FileEncoding>()
{
FileEncoding.UTF8,
FileEncoding.Default,
FileEncoding.ASCII,
FileEncoding.WINDOWS1252,
FileEncoding.Other
};

_source.FileEncodingString = "windows-1251";

foreach (var encoding in encodings)
{
_source.Encoding = encoding;

// Connection string
var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success, $"Encoding: {encoding}");
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));

// OAuth
_destination.BlobName = $"testblob_{Guid.NewGuid()}";
_destination.ConnectionMethod = ConnectionMethod.OAuth2;
result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success, $"Encoding: {encoding}");
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));
}
}

[Test]
public async Task WriteBlob_TestCreateContainer()
{
_destination.CreateContainerIfItDoesNotExist = true;

// Connection string
_destination.ContainerName = $"test-container{DateTime.Now.ToString("mmssffffff", CultureInfo.InvariantCulture)}";
var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);

var blobServiceClient = new BlobServiceClient(_destination.ConnectionString);
var containerClient = blobServiceClient.GetBlobContainerClient(_destination.ContainerName);
Assert.IsTrue(containerClient.Exists());

await DeleteBlobContainer(_destination.ContainerName);

// OAuth
_destination.ConnectionString = null;
_destination.ContainerName = $"test-container{DateTime.Now.ToString("mmssffffff", CultureInfo.InvariantCulture)}";
_destination.BlobName = $"testblob_{Guid.NewGuid()}";
_destination.ConnectionMethod = ConnectionMethod.OAuth2;
result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));

containerClient = blobServiceClient.GetBlobContainerClient(_destination.ContainerName);
Assert.IsTrue(containerClient.Exists());

await DeleteBlobContainer(_destination.ContainerName);
}

[Test]
public void WriteBlob_InvalidConnectionString_ShouldThrowException()
{
_destination.ConnectionString = "DefaultEndpointsProtocol=https;AccountName=invalid;AccountKey=InvalidAccountKey;EndpointSuffix=core.windows.net"; // Simulate an invalid connection string

var ex = Assert.ThrowsAsync<FormatException>(async () => await AzureBlobStorage.WriteBlob(_source, _destination, _options, default));
Assert.AreEqual("No valid combination of account information found.", ex.Message);
}

[Test]
public void WriteBlob_InvalidOAuth2_ShouldThrowException()
{
_destination.ConnectionMethod = ConnectionMethod.OAuth2;
_destination.ClientSecret = "InvalidClientSecret";

var ex = Assert.ThrowsAsync<AuthenticationFailedException>(async () => await AzureBlobStorage.WriteBlob(_source, _destination, _options, default));
Assert.IsTrue(ex.Message.Contains("ClientSecretCredential authentication failed"));
}

[Test]
public async Task WriteBlob_Tags()
{
_destination.Tags = _tags;

// Connection string
var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));
}

[Test]
public async Task WriteBlob_SasToken()
{
_destination.ConnectionMethod = ConnectionMethod.SASToken;
_destination.SASToken = _sasToken;
_destination.ContainerName = _container;

var result = await AzureBlobStorage.WriteBlob(_source, _destination, _options, default);
Assert.IsTrue(result.Success);
Assert.IsTrue(await BlobExists(_destination.ContainerName, _destination.BlobName, _testContent));
}

private async static Task CreateBlobContainer(string connectionString, string containerName)
{
var blobServiceClient = new BlobServiceClient(connectionString);
var container = blobServiceClient.GetBlobContainerClient(containerName);
await container.CreateIfNotExistsAsync(PublicAccessType.None, null, null);
}

private async Task DeleteBlobContainer(string containerName)
{
var blobServiceClient = new BlobServiceClient(_connectionString);
var container = blobServiceClient.GetBlobContainerClient(containerName);
await container.DeleteIfExistsAsync();
}

private async Task<bool> BlobExists(string containerName, string blobName, string expected)
{
var blobServiceClient = new BlobServiceClient(_connectionString);
var container = blobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlobClient(blobName);
if (!blob.Exists())
return false;

var blobClient = new BlobClient(_connectionString, _destination.ContainerName, _destination.BlobName);
var blobDownload = await blobClient.DownloadAsync();

using var reader = new StreamReader(blobDownload.Value.Content);
var content = await reader.ReadToEndAsync();
return content == expected;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.AzureBlobStorage.WriteBlob", "Frends.AzureBlobStorage.WriteBlob\Frends.AzureBlobStorage.WriteBlob.csproj", "{35C305C0-8108-4A98-BB1D-AFE5C926239E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.AzureBlobStorage.WriteBlob.Tests", "Frends.AzureBlobStorage.WriteBlob.Tests\Frends.AzureBlobStorage.WriteBlob.Tests.csproj", "{8CA92187-8E4F-4414-803B-EC899479022E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{78F7F22E-6E20-4BCE-8362-0C558568B729}"
ProjectSection(SolutionItems) = preProject
CHANGELOG.md = CHANGELOG.md
..\.github\workflows\WriteBlob_build_and_test_on_main.yml = ..\.github\workflows\WriteBlob_build_and_test_on_main.yml
..\.github\workflows\WriteBlob_build_and_test_on_push.yml = ..\.github\workflows\WriteBlob_build_and_test_on_push.yml
..\.github\workflows\WriteBlob_release.yml = ..\.github\workflows\WriteBlob_release.yml
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.Build.0 = Release|Any CPU
{8CA92187-8E4F-4414-803B-EC899479022E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CA92187-8E4F-4414-803B-EC899479022E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CA92187-8E4F-4414-803B-EC899479022E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CA92187-8E4F-4414-803B-EC899479022E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55BC6629-85C9-48D8-8CA2-B0046AF1AF4B}
EndGlobalSection
EndGlobal
Loading
Loading