Files
pos-system/services/storage-service-net/tests/StorageService.FunctionalTests/ApiTests/FileSharingApiTests.cs

346 lines
10 KiB
C#

using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.FileShareAggregate;
using StorageService.Infrastructure.Persistence;
namespace StorageService.FunctionalTests.ApiTests;
/// <summary>
/// EN: Functional tests for File Sharing API endpoints.
/// VI: Functional tests cho File Sharing API endpoints.
/// </summary>
public class FileSharingApiTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory _factory;
public FileSharingApiTests(CustomWebApplicationFactory factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
// EN: Add mock authentication header / VI: Thêm mock authentication header
_client.DefaultRequestHeaders.Add("X-User-Id", "test-user-123");
}
#region CreateShare Tests
[Fact]
public async Task CreateShare_ValidRequest_ReturnsShareLink()
{
// Arrange
var fileId = await SeedTestFile("shareable-file.pdf");
var request = new CreateShareRequest(
FileId: fileId,
Permission: "read");
// Act
var response = await _client.PostAsJsonAsync("/api/v1/storage/shares", request);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.Created);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadFromJsonAsync<CreateShareResponse>();
content.Should().NotBeNull();
content!.ShareToken.Should().NotBeNullOrEmpty();
content.ShareUrl.Should().NotBeNullOrEmpty();
}
}
[Fact]
public async Task CreateShare_WithPassword_ReturnsProtectedShare()
{
// Arrange
var fileId = await SeedTestFile("protected-file.pdf");
var request = new CreateShareRequest(
FileId: fileId,
Permission: "read",
Password: "SecretP@ss123");
// Act
var response = await _client.PostAsJsonAsync("/api/v1/storage/shares", request);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.Created);
}
[Fact]
public async Task CreateShare_WithExpiration_SetsExpiryDate()
{
// Arrange
var fileId = await SeedTestFile("expiring-file.pdf");
var expiresAt = DateTime.UtcNow.AddDays(7);
var request = new CreateShareRequest(
FileId: fileId,
Permission: "read",
ExpiresAt: expiresAt);
// Act
var response = await _client.PostAsJsonAsync("/api/v1/storage/shares", request);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.Created);
}
[Fact]
public async Task CreateShare_WithMaxDownloads_SetsLimit()
{
// Arrange
var fileId = await SeedTestFile("limited-file.pdf");
var request = new CreateShareRequest(
FileId: fileId,
Permission: "read",
MaxDownloads: 5);
// Act
var response = await _client.PostAsJsonAsync("/api/v1/storage/shares", request);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.Created);
}
[Fact]
public async Task CreateShare_FileNotFound_Returns404()
{
// Arrange
var request = new CreateShareRequest(
FileId: Guid.NewGuid(), // Non-existent
Permission: "read");
// Act
var response = await _client.PostAsJsonAsync("/api/v1/storage/shares", request);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.NotFound, HttpStatusCode.BadRequest);
}
#endregion
#region AccessShare Tests
[Fact]
public async Task AccessShare_ValidToken_ReturnsFileInfo()
{
// Arrange
var shareToken = await SeedTestShare();
// Act
// EN: Public share access - no auth needed
// VI: Truy cập share công khai - không cần auth
var unauthClient = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
var response = await unauthClient.GetAsync($"/api/v1/storage/shares/public/{shareToken}");
// Assert
// EN: Should return file info or download URL
// VI: Nên trả về thông tin file hoặc download URL
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
public async Task AccessShare_InvalidToken_Returns404()
{
// Arrange
var invalidToken = "invalid-share-token-12345";
// Act
var unauthClient = _factory.CreateClient();
var response = await unauthClient.GetAsync($"/api/v1/storage/shares/public/{invalidToken}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task AccessShare_ExpiredToken_Returns410()
{
// Arrange
var expiredToken = await SeedTestShare(expired: true);
// Act
var unauthClient = _factory.CreateClient();
var response = await unauthClient.GetAsync($"/api/v1/storage/shares/public/{expiredToken}");
// Assert
// EN: 404 or 410 depending on implementation
// VI: 404 hoặc 410 tùy thuộc vào implementation
response.StatusCode.Should().BeOneOf(HttpStatusCode.NotFound, HttpStatusCode.Gone);
}
#endregion
#region RevokeShare Tests
[Fact]
public async Task RevokeShare_ValidRequest_Returns204()
{
// Arrange
var shareId = await SeedTestShareAndGetId();
// Act
var response = await _client.DeleteAsync($"/api/v1/storage/shares/{shareId}");
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.NoContent, HttpStatusCode.OK);
}
[Fact]
public async Task RevokeShare_NotFound_Returns404()
{
// Arrange
var nonExistentId = Guid.NewGuid();
// Act
var response = await _client.DeleteAsync($"/api/v1/storage/shares/{nonExistentId}");
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.NotFound, HttpStatusCode.BadRequest);
}
#endregion
#region GetUserShares Tests
[Fact]
public async Task GetUserShares_WithShares_ReturnsList()
{
// Arrange
await SeedTestShareAndGetId();
await SeedTestShareAndGetId();
// Act
var response = await _client.GetAsync("/api/v1/storage/shares");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
#endregion
#region Helper Methods
private async Task<Guid> SeedTestFile(string fileName)
{
using var scope = _factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<StorageServiceContext>();
var file = new StorageFile(
fileName: fileName,
bucketName: "test-bucket",
objectKey: $"private/test-user-123/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid():N}_{fileName}",
contentType: "application/pdf",
fileSizeBytes: 1024,
userId: "test-user-123",
provider: StorageProvider.MinIO,
accessLevel: FileAccessLevel.Private);
context.Files.Add(file);
await context.SaveChangesAsync();
return file.Id;
}
private async Task<string> SeedTestShare(bool expired = false)
{
using var scope = _factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<StorageServiceContext>();
// First create a file
var file = new StorageFile(
fileName: "share-test.pdf",
bucketName: "test-bucket",
objectKey: $"private/test-user-123/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid():N}_share-test.pdf",
contentType: "application/pdf",
fileSizeBytes: 1024,
userId: "test-user-123",
provider: StorageProvider.MinIO,
accessLevel: FileAccessLevel.Private);
context.Files.Add(file);
await context.SaveChangesAsync();
// Create share
var expiresAt = expired ? DateTime.UtcNow.AddDays(-1) : DateTime.UtcNow.AddDays(7);
var share = new FileShare(
fileId: file.Id,
sharedBy: "test-user-123",
permission: SharePermission.Read,
expiresAt: expiresAt);
context.FileShares.Add(share);
await context.SaveChangesAsync();
return share.ShareToken;
}
private async Task<Guid> SeedTestShareAndGetId()
{
using var scope = _factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<StorageServiceContext>();
// First create a file
var file = new StorageFile(
fileName: "share-test.pdf",
bucketName: "test-bucket",
objectKey: $"private/test-user-123/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid():N}_share-test.pdf",
contentType: "application/pdf",
fileSizeBytes: 1024,
userId: "test-user-123",
provider: StorageProvider.MinIO,
accessLevel: FileAccessLevel.Private);
context.Files.Add(file);
await context.SaveChangesAsync();
// Create share
var share = new FileShare(
fileId: file.Id,
sharedBy: "test-user-123",
permission: SharePermission.Read);
context.FileShares.Add(share);
await context.SaveChangesAsync();
return share.Id;
}
#endregion
}
#region Request/Response DTOs
/// <summary>
/// EN: Create share request DTO for tests.
/// VI: DTO request tạo share cho tests.
/// </summary>
public record CreateShareRequest(
Guid FileId,
string Permission,
string? SharedWith = null,
string? Password = null,
DateTime? ExpiresAt = null,
int? MaxDownloads = null);
/// <summary>
/// EN: Create share response DTO for tests.
/// VI: DTO response tạo share cho tests.
/// </summary>
public record CreateShareResponse(
Guid ShareId,
string ShareToken,
string ShareUrl);
#endregion