diff --git a/ManagedCode.Storage.Azure/BlobStream.cs b/ManagedCode.Storage.Azure/BlobStream.cs index f7d46b9..af68ee2 100644 --- a/ManagedCode.Storage.Azure/BlobStream.cs +++ b/ManagedCode.Storage.Azure/BlobStream.cs @@ -37,18 +37,19 @@ public override long Length { get { - var realLength = 0L; - var metadata = _pageBlob.GetProperties().Value.Metadata; + var properties = _pageBlob.GetProperties(); + var metadata = properties.Value.Metadata; if (metadata.TryGetValue(MetadataLengthKey, out var length)) { - if (long.TryParse(length, out realLength)) + if (long.TryParse(length, out var realLength)) { return realLength; } } - - SetLengthInternal(realLength); - return realLength; + + var contentLenght = properties.Value.ContentLength; + SetLengthInternal(contentLenght); + return contentLenght; } } diff --git a/ManagedCode.Storage.Tests/Common/FileHelper.cs b/ManagedCode.Storage.Tests/Common/FileHelper.cs index 4d5c8c7..34df22b 100644 --- a/ManagedCode.Storage.Tests/Common/FileHelper.cs +++ b/ManagedCode.Storage.Tests/Common/FileHelper.cs @@ -25,6 +25,34 @@ public static LocalFile GenerateLocalFile(string fileName, int byteSize) return localFile; } + public static LocalFile GenerateLocalFileWithData(LocalFile file, int sizeInBytes) + { + using (var fileStream = file.FileStream) + { + Random random = new Random(); + byte[] buffer = new byte[1024]; // Buffer for writing in chunks + + while (sizeInBytes > 0) + { + int bytesToWrite = (int) Math.Min(sizeInBytes, buffer.Length); + + for (int i = 0; i < bytesToWrite; i++) + { + buffer[i] = (byte) random.Next(65, 91); // 'A' to 'Z' + if (random.Next(2) == 0) + { + buffer[i] = (byte) random.Next(97, 123); // 'a' to 'z' + } + } + + fileStream.Write(buffer, 0, bytesToWrite); + sizeInBytes -= bytesToWrite; + } + } + + return file; + } + public static IFormFile GenerateFormFile(string fileName, int byteSize) { var localFile = GenerateLocalFile(fileName, byteSize); diff --git a/ManagedCode.Storage.Tests/Storages/Azure/AzureBlobStreamTests.cs b/ManagedCode.Storage.Tests/Storages/Azure/AzureBlobStreamTests.cs new file mode 100644 index 0000000..9861408 --- /dev/null +++ b/ManagedCode.Storage.Tests/Storages/Azure/AzureBlobStreamTests.cs @@ -0,0 +1,175 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using ManagedCode.Storage.Azure; +using ManagedCode.Storage.Core.Models; +using ManagedCode.Storage.Tests.Common; +using Microsoft.Extensions.DependencyInjection; +using Testcontainers.Azurite; +using Xunit; + +namespace ManagedCode.Storage.Tests.Storages.Azure; + +public class AzureBlobStreamTests : StreamTests +{ + protected override AzuriteContainer Build() + { + return new AzuriteBuilder() + .WithImage("mcr.microsoft.com/azure-storage/azurite:3.26.0") + .Build(); + } + + protected override ServiceProvider ConfigureServices() + { + return AzureConfigurator.ConfigureServices(Container.GetConnectionString()); + } + + [Fact] + public async Task ReadStreamWithStreamReader_WhenFileExists_ReturnData() + { + // Arrange + var directory = "test-directory"; + var fileSizeInBytes = 10; + await using var localFile = LocalFile.FromRandomNameWithExtension(".txt"); + FileHelper.GenerateLocalFileWithData(localFile, fileSizeInBytes); + var storage = (IAzureStorage) Storage; + + UploadOptions options = new() { FileName = localFile.Name, Directory = directory }; + await using var localFileStream = localFile.FileInfo.OpenRead(); + var result = await storage.UploadAsync(localFileStream, options); + + await using var blobStream = storage.GetBlobStream(result.Value.FullName); + + // Act + using var streamReader = new StreamReader(blobStream); + var content = await streamReader.ReadToEndAsync(); + + // Assert + await using var fileStream = localFile.FileInfo.OpenRead(); + using var fileReader = new StreamReader(fileStream); + var fileContent = await fileReader.ReadToEndAsync(); + content.Should().NotBeNullOrEmpty(); + fileContent.Should().NotBeNullOrEmpty(); + content.Should().Be(fileContent); + } + + [Fact] + public async Task ReadStream_WhenFileExists_ReturnData() + { + // Arrange + var directory = "test-directory"; + var fileSizeInBytes = 10; + await using var localFile = LocalFile.FromRandomNameWithExtension(".txt"); + FileHelper.GenerateLocalFileWithData(localFile, fileSizeInBytes); + var storage = (IAzureStorage) Storage; + + UploadOptions options = new() { FileName = localFile.Name, Directory = directory }; + await using var fileStream = localFile.FileInfo.OpenRead(); + var result = await storage.UploadAsync(fileStream, options); + + await using var blobStream = storage.GetBlobStream(result.Value.FullName); + + var chunkSize = (int) blobStream.Length / 2; + var chunk1 = new byte[chunkSize]; + var chunk2 = new byte[chunkSize]; + + // Act + var bytesReadForChunk1 = await blobStream.ReadAsync(chunk1, 0, chunkSize); + var bytesReadForChunk2 = await blobStream.ReadAsync(chunk2, 0, chunkSize); + + // Assert + bytesReadForChunk1.Should().Be(chunkSize); + bytesReadForChunk2.Should().Be(chunkSize); + chunk1.Should().NotBeNullOrEmpty().And.HaveCount(chunkSize); + chunk2.Should().NotBeNullOrEmpty().And.HaveCount(chunkSize); + } + + [Fact] + public async Task ReadStream_WhenFileDoesNotExists_ReturnNoData() + { + // Arrange + var directory = "test-directory"; + var storage = (IAzureStorage) Storage; + await storage.CreateContainerAsync(); + var fullFileName = $"{directory}/{Guid.NewGuid()}.txt"; + + await using var blobStream = storage.GetBlobStream(fullFileName); + var chunk = new byte[4]; + + // Act + var bytesRead = await blobStream.ReadAsync(chunk, 0, 4); + + // Assert + bytesRead.Should().Be(0); + chunk.Should().NotBeNullOrEmpty(); + chunk.Should().AllBeEquivalentTo(0); + } + + [Fact] + public async Task WriteStreamWithStreamWriter_SaveData() + { + // Arrange + var directory = "test-directory"; + await using var localFile = LocalFile.FromRandomNameWithExtension(".txt"); + var fileSizeInBytes = 10; + FileHelper.GenerateLocalFileWithData(localFile, fileSizeInBytes); + var fullFileName = $"{directory}/{localFile.FileInfo.FullName}"; + + var storage = (IAzureStorage) Storage; + + await storage.CreateContainerAsync(); + + // Act + await using (var blobStream = storage.GetBlobStream(fullFileName)) + { + await using (var localFileStream = localFile.FileStream) + { + await localFileStream.CopyToAsync(blobStream); + } + } + + // Assert + var fileResult = await storage.DownloadAsync(fullFileName); + fileResult.IsSuccess.Should().BeTrue(); + fileResult.Value.Should().NotBeNull(); + await using var fileStream = fileResult.Value.FileStream; + using var streamReader = new StreamReader(fileStream); + var fileContent = await streamReader.ReadLineAsync(); + fileContent.Should().NotBeNullOrEmpty(); + } + + [Fact] + public async Task Seek_WhenFileExists_ReturnData() + { + // Arrange + var directory = "test-directory"; + var fileSizeInBytes = 10; + await using var localFile = LocalFile.FromRandomNameWithExtension(".txt"); + FileHelper.GenerateLocalFileWithData(localFile, fileSizeInBytes); + var storage = (IAzureStorage) Storage; + + UploadOptions options = new() { FileName = localFile.Name, Directory = directory }; + await using var localFileStream = localFile.FileInfo.OpenRead(); + var result = await storage.UploadAsync(localFileStream, options); + + await using var blobStream = storage.GetBlobStream(result.Value.FullName); + + // Act + var seekInPosition = fileSizeInBytes / 2; + blobStream.Seek(seekInPosition, SeekOrigin.Current); + var buffer = new byte[seekInPosition]; + var bytesRead = await blobStream.ReadAsync(buffer); + + // Assert + bytesRead.Should().Be(seekInPosition); + await using var fileStream = localFile.FileInfo.OpenRead(); + using var fileReader = new StreamReader(fileStream); + var fileContent = await fileReader.ReadToEndAsync(); + var content = Encoding.UTF8.GetString(buffer); + content.Should().NotBeNullOrEmpty(); + var trimmedFileContent = fileContent.Remove(0, seekInPosition); + content.Should().Be(trimmedFileContent); + } +} \ No newline at end of file diff --git a/ManagedCode.Storage.Tests/Storages/StreamTests.cs b/ManagedCode.Storage.Tests/Storages/StreamTests.cs new file mode 100644 index 0000000..ae4f8de --- /dev/null +++ b/ManagedCode.Storage.Tests/Storages/StreamTests.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using DotNet.Testcontainers.Containers; +using ManagedCode.Storage.Tests.Common; +using Xunit; + +namespace ManagedCode.Storage.Tests.Storages; + +public abstract class StreamTests : BaseContainer + where T : IContainer +{ + +} \ No newline at end of file