mirror of
https://github.com/bitwarden/server
synced 2026-02-12 14:33:49 +00:00
[PM-31787] Users can access the sends after the limit was reached (#6958)
* fix file type send increment behavior * fix text send access increment behavior * fix & update tests * cleanup unused service * fix broken test constructor expecting unused service
This commit is contained in:
@@ -240,6 +240,11 @@ public class SendsController : Controller
|
||||
throw new BadRequestException("Could not locate send");
|
||||
}
|
||||
|
||||
if (!INonAnonymousSendCommand.SendCanBeAccessed(send))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var sendResponse = new SendAccessResponseModel(send);
|
||||
if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault())
|
||||
{
|
||||
@@ -247,9 +252,19 @@ public class SendsController : Controller
|
||||
sendResponse.CreatorIdentifier = creator.Email;
|
||||
}
|
||||
|
||||
send.AccessCount++;
|
||||
await _sendRepository.ReplaceAsync(send);
|
||||
await _pushNotificationService.PushSyncSendUpdateAsync(send);
|
||||
/*
|
||||
* AccessCount is incremented differently for File and Text Send types:
|
||||
* - Text Sends are incremented at every access
|
||||
* - File Sends are incremented only when the file is downloaded
|
||||
*
|
||||
* Note that this endpoint is initially called for all Send types
|
||||
*/
|
||||
if (send.Type == SendType.Text)
|
||||
{
|
||||
send.AccessCount++;
|
||||
await _sendRepository.ReplaceAsync(send);
|
||||
await _pushNotificationService.PushSyncSendUpdateAsync(send);
|
||||
}
|
||||
|
||||
return new ObjectResult(sendResponse);
|
||||
}
|
||||
@@ -267,11 +282,12 @@ public class SendsController : Controller
|
||||
throw new BadRequestException("Could not locate send");
|
||||
}
|
||||
|
||||
var url = await _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
|
||||
send.AccessCount++;
|
||||
await _sendRepository.ReplaceAsync(send);
|
||||
await _pushNotificationService.PushSyncSendUpdateAsync(send);
|
||||
if (result.Equals(SendAccessResult.Denied))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new ObjectResult(new SendFileDownloadDataResponseModel() { Id = fileId, Url = url });
|
||||
}
|
||||
|
||||
@@ -47,7 +47,45 @@ public interface INonAnonymousSendCommand
|
||||
/// <returns><see langword="true" /> when the file is confirmed, otherwise <see langword="false" /></returns>
|
||||
/// <remarks>
|
||||
/// When a file size cannot be confirmed, we assume we're working with a rogue client. The send is deleted out of
|
||||
/// an abundance of caution.
|
||||
/// an abundance of caution.
|
||||
/// </remarks>
|
||||
Task<bool> ConfirmFileSize(Send send);
|
||||
|
||||
/// <summary>
|
||||
/// If a File type Send can be downloaded, retrieves the download URL.
|
||||
/// </summary>
|
||||
/// <param name="send">The <see cref="Send" /> this command acts upon</param>
|
||||
/// <param name="fileId">The fileId to be downloaded</param>
|
||||
/// <returns>
|
||||
/// A tuple wrapping the download URL string and <see cref="SendAccessResult" /> indicating whether access was granted
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method is intended for authenticated endpoints where authentication has already been validated.
|
||||
/// Returns <see cref="SendAccessResult.Denied" /> when the Send is disabled, MaxAccessCount has been reached,
|
||||
/// expiration date has passed, or deletion date has been reached.
|
||||
/// </remarks>
|
||||
Task<(string, SendAccessResult)> GetSendFileDownloadUrlAsync(Send send, string fileId);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="Send" /> can be accessed based on its current state.
|
||||
/// </summary>
|
||||
/// <param name="send">The <see cref="Send" /> to evaluate for access</param>
|
||||
/// <returns><see langword="true" /> if the Send can be accessed, otherwise <see langword="false" /></returns>
|
||||
/// <remarks>
|
||||
/// This method checks if the Send is disabled, if MaxAccessCount has been reached,
|
||||
/// if the expiration date has passed, or if the deletion date has been reached.
|
||||
/// </remarks>
|
||||
static bool SendCanBeAccessed(Send send)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (send.MaxAccessCount.GetValueOrDefault(int.MaxValue) <= send.AccessCount ||
|
||||
send.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < now ||
|
||||
send.Disabled ||
|
||||
send.DeletionDate < now)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ public class NonAnonymousSendCommand : INonAnonymousSendCommand
|
||||
public NonAnonymousSendCommand(ISendRepository sendRepository,
|
||||
ISendFileStorageService sendFileStorageService,
|
||||
IPushNotificationService pushNotificationService,
|
||||
ISendAuthorizationService sendAuthorizationService,
|
||||
ISendValidationService sendValidationService,
|
||||
ISendCoreHelperService sendCoreHelperService,
|
||||
ILogger<NonAnonymousSendCommand> logger)
|
||||
@@ -181,4 +180,21 @@ public class NonAnonymousSendCommand : INonAnonymousSendCommand
|
||||
return valid;
|
||||
}
|
||||
|
||||
public async Task<(string, SendAccessResult)> GetSendFileDownloadUrlAsync(Send send, string fileId)
|
||||
{
|
||||
if (send.Type != SendType.File)
|
||||
{
|
||||
throw new BadRequestException("Can only get a download URL for a file type of Send");
|
||||
}
|
||||
|
||||
if (!INonAnonymousSendCommand.SendCanBeAccessed(send))
|
||||
{
|
||||
return (null, SendAccessResult.Denied);
|
||||
}
|
||||
|
||||
send.AccessCount++;
|
||||
await _sendRepository.ReplaceAsync(send);
|
||||
await _pushNotificationService.PushSyncSendUpdateAsync(send);
|
||||
return (await _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId), SendAccessResult.Granted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,6 +903,106 @@ public class SendsControllerTests : IDisposable
|
||||
Assert.Equal(creator.Email, response.CreatorIdentifier);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task AccessUsingAuth_WithDisabledSend_ThrowsNotFoundException(Guid sendId)
|
||||
{
|
||||
var send = new Send
|
||||
{
|
||||
Id = sendId,
|
||||
Type = SendType.Text,
|
||||
Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)),
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
Disabled = true,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sut.AccessUsingAuth());
|
||||
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _userService.DidNotReceive().GetUserByIdAsync(Arg.Any<Guid>());
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task AccessUsingAuth_WithMaxAccessCountReached_ThrowsNotFoundException(Guid sendId)
|
||||
{
|
||||
var send = new Send
|
||||
{
|
||||
Id = sendId,
|
||||
Type = SendType.Text,
|
||||
Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)),
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
Disabled = false,
|
||||
AccessCount = 10,
|
||||
MaxAccessCount = 10
|
||||
};
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sut.AccessUsingAuth());
|
||||
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _userService.DidNotReceive().GetUserByIdAsync(Arg.Any<Guid>());
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task AccessUsingAuth_WithExpiredSend_ThrowsNotFoundException(Guid sendId)
|
||||
{
|
||||
var send = new Send
|
||||
{
|
||||
Id = sendId,
|
||||
Type = SendType.Text,
|
||||
Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)),
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = DateTime.UtcNow.AddDays(-1), // Expired yesterday
|
||||
Disabled = false,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sut.AccessUsingAuth());
|
||||
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _userService.DidNotReceive().GetUserByIdAsync(Arg.Any<Guid>());
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task AccessUsingAuth_WithDeletionDatePassed_ThrowsNotFoundException(Guid sendId)
|
||||
{
|
||||
var send = new Send
|
||||
{
|
||||
Id = sendId,
|
||||
Type = SendType.Text,
|
||||
Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)),
|
||||
DeletionDate = DateTime.UtcNow.AddDays(-1), // Deletion date has passed
|
||||
ExpirationDate = null,
|
||||
Disabled = false,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sut.AccessUsingAuth());
|
||||
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _userService.DidNotReceive().GetUserByIdAsync(Arg.Any<Guid>());
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task GetSendFileDownloadDataUsingAuth_WithValidFileId_ReturnsDownloadUrl(
|
||||
Guid sendId, string fileId, string expectedUrl)
|
||||
@@ -922,7 +1022,8 @@ public class SendsControllerTests : IDisposable
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
_sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId).Returns(expectedUrl);
|
||||
_nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId)
|
||||
.Returns((expectedUrl, SendAccessResult.Granted));
|
||||
|
||||
var result = await _sut.GetSendFileDownloadDataUsingAuth(fileId);
|
||||
|
||||
@@ -932,7 +1033,7 @@ public class SendsControllerTests : IDisposable
|
||||
Assert.Equal(fileId, response.Id);
|
||||
Assert.Equal(expectedUrl, response.Url);
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _sendFileStorageService.Received(1).GetSendFileDownloadUrlAsync(send, fileId);
|
||||
await _nonAnonymousSendCommand.Received(1).GetSendFileDownloadUrlAsync(send, fileId);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
@@ -948,13 +1049,13 @@ public class SendsControllerTests : IDisposable
|
||||
|
||||
Assert.Equal("Could not locate send", exception.Message);
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _sendFileStorageService.DidNotReceive()
|
||||
await _nonAnonymousSendCommand.DidNotReceive()
|
||||
.GetSendFileDownloadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task GetSendFileDownloadDataUsingAuth_WithTextSend_StillReturnsResponse(
|
||||
Guid sendId, string fileId, string expectedUrl)
|
||||
public async Task GetSendFileDownloadDataUsingAuth_WithTextSend_ThrowsBadRequestException(
|
||||
Guid sendId, string fileId)
|
||||
{
|
||||
var send = new Send
|
||||
{
|
||||
@@ -970,15 +1071,44 @@ public class SendsControllerTests : IDisposable
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
_sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId).Returns(expectedUrl);
|
||||
_nonAnonymousSendCommand
|
||||
.When(x => x.GetSendFileDownloadUrlAsync(send, fileId))
|
||||
.Do(x => throw new BadRequestException("Can only get a download URL for a file type of Send"));
|
||||
|
||||
var result = await _sut.GetSendFileDownloadDataUsingAuth(fileId);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _sut.GetSendFileDownloadDataUsingAuth(fileId));
|
||||
|
||||
Assert.NotNull(result);
|
||||
var objectResult = Assert.IsType<ObjectResult>(result);
|
||||
var response = Assert.IsType<SendFileDownloadDataResponseModel>(objectResult.Value);
|
||||
Assert.Equal(fileId, response.Id);
|
||||
Assert.Equal(expectedUrl, response.Url);
|
||||
Assert.Equal("Can only get a download URL for a file type of Send", exception.Message);
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _nonAnonymousSendCommand.Received(1).GetSendFileDownloadUrlAsync(send, fileId);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task GetSendFileDownloadDataUsingAuth_WithAccessDenied_ThrowsNotFoundException(
|
||||
Guid sendId, string fileId)
|
||||
{
|
||||
var fileData = new SendFileData("Test File", "Notes", "document.pdf") { Id = fileId, Size = 2048 };
|
||||
var send = new Send
|
||||
{
|
||||
Id = sendId,
|
||||
Type = SendType.File,
|
||||
Data = JsonSerializer.Serialize(fileData),
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
Disabled = false,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
var user = CreateUserWithSendIdClaim(sendId);
|
||||
_sut.ControllerContext = CreateControllerContextWithUser(user);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
_nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId)
|
||||
.Returns((null, SendAccessResult.Denied));
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sut.GetSendFileDownloadDataUsingAuth(fileId));
|
||||
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
await _nonAnonymousSendCommand.Received(1).GetSendFileDownloadUrlAsync(send, fileId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures.Commands;
|
||||
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -28,7 +29,6 @@ public class NonAnonymousSendCommandTests
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly ISendFileStorageService _sendFileStorageService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly ISendAuthorizationService _sendAuthorizationService;
|
||||
private readonly ISendValidationService _sendValidationService;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
@@ -42,7 +42,6 @@ public class NonAnonymousSendCommandTests
|
||||
_sendRepository = Substitute.For<ISendRepository>();
|
||||
_sendFileStorageService = Substitute.For<ISendFileStorageService>();
|
||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||
_sendAuthorizationService = Substitute.For<ISendAuthorizationService>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_sendValidationService = Substitute.For<ISendValidationService>();
|
||||
_currentContext = Substitute.For<ICurrentContext>();
|
||||
@@ -53,7 +52,6 @@ public class NonAnonymousSendCommandTests
|
||||
_sendRepository,
|
||||
_sendFileStorageService,
|
||||
_pushNotificationService,
|
||||
_sendAuthorizationService,
|
||||
_sendValidationService,
|
||||
_sendCoreHelperService,
|
||||
_logger
|
||||
@@ -1093,4 +1091,329 @@ public class NonAnonymousSendCommandTests
|
||||
|
||||
Assert.Equal("File received does not match expected file length.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSendFileDownloadUrlAsync_WithTextSend_ThrowsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.Text,
|
||||
UserId = Guid.NewGuid()
|
||||
};
|
||||
var fileId = "somefile123";
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
_nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId));
|
||||
|
||||
Assert.Equal("Can only get a download URL for a file type of Send", exception.Message);
|
||||
|
||||
// Verify no storage service methods were called
|
||||
await _sendFileStorageService.DidNotReceive()
|
||||
.GetSendFileDownloadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSendFileDownloadUrlAsync_WithDisabledSend_ReturnsDenied()
|
||||
{
|
||||
// Arrange
|
||||
var fileId = "file123";
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.File,
|
||||
UserId = Guid.NewGuid(),
|
||||
Disabled = true,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(url);
|
||||
Assert.Equal(SendAccessResult.Denied, result);
|
||||
|
||||
// Verify no repository updates occurred
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any<Send>());
|
||||
await _sendFileStorageService.DidNotReceive()
|
||||
.GetSendFileDownloadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSendFileDownloadUrlAsync_WithMaxAccessCountReached_ReturnsDenied()
|
||||
{
|
||||
// Arrange
|
||||
var fileId = "file123";
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.File,
|
||||
UserId = Guid.NewGuid(),
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 5,
|
||||
MaxAccessCount = 5
|
||||
};
|
||||
|
||||
// Act
|
||||
var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(url);
|
||||
Assert.Equal(SendAccessResult.Denied, result);
|
||||
|
||||
// Verify no repository updates occurred
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any<Send>());
|
||||
await _sendFileStorageService.DidNotReceive()
|
||||
.GetSendFileDownloadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSendFileDownloadUrlAsync_WithExpiredSend_ReturnsDenied()
|
||||
{
|
||||
// Arrange
|
||||
var fileId = "file123";
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.File,
|
||||
UserId = Guid.NewGuid(),
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = DateTime.UtcNow.AddDays(-1), // Expired yesterday
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(url);
|
||||
Assert.Equal(SendAccessResult.Denied, result);
|
||||
|
||||
// Verify no repository updates occurred
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any<Send>());
|
||||
await _sendFileStorageService.DidNotReceive()
|
||||
.GetSendFileDownloadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSendFileDownloadUrlAsync_WithDeletionDatePassed_ReturnsDenied()
|
||||
{
|
||||
// Arrange
|
||||
var fileId = "file123";
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.File,
|
||||
UserId = Guid.NewGuid(),
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(-1), // Deletion date has passed
|
||||
ExpirationDate = null,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(url);
|
||||
Assert.Equal(SendAccessResult.Denied, result);
|
||||
|
||||
// Verify no repository updates occurred
|
||||
await _sendRepository.DidNotReceive().ReplaceAsync(Arg.Any<Send>());
|
||||
await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any<Send>());
|
||||
await _sendFileStorageService.DidNotReceive()
|
||||
.GetSendFileDownloadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSendFileDownloadUrlAsync_WithValidSend_ReturnsUrlAndIncrementsAccessCount()
|
||||
{
|
||||
// Arrange
|
||||
var fileId = "file123";
|
||||
var expectedUrl = "https://download.example.com/file123";
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.File,
|
||||
UserId = Guid.NewGuid(),
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 3,
|
||||
MaxAccessCount = 10
|
||||
};
|
||||
|
||||
_sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId).Returns(expectedUrl);
|
||||
|
||||
// Act
|
||||
var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedUrl, url);
|
||||
Assert.Equal(SendAccessResult.Granted, result);
|
||||
|
||||
// Verify access count was incremented
|
||||
Assert.Equal(4, send.AccessCount);
|
||||
|
||||
// Verify repository was updated
|
||||
await _sendRepository.Received(1).ReplaceAsync(send);
|
||||
await _pushNotificationService.Received(1).PushSyncSendUpdateAsync(send);
|
||||
|
||||
// Verify file storage service was called
|
||||
await _sendFileStorageService.Received(1).GetSendFileDownloadUrlAsync(send, fileId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithDisabledSend_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = true,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithMaxAccessCountReached_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 10,
|
||||
MaxAccessCount = 10
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithExpiredSend_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = DateTime.UtcNow.AddDays(-1),
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithDeletionDatePassed_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(-1),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithValidSend_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = DateTime.UtcNow.AddDays(7),
|
||||
AccessCount = 5,
|
||||
MaxAccessCount = 10
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithNullMaxAccessCount_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 100,
|
||||
MaxAccessCount = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCanBeAccessed_WithNullExpirationDate_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var send = new Send
|
||||
{
|
||||
Disabled = false,
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7),
|
||||
ExpirationDate = null,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = 10
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = INonAnonymousSendCommand.SendCanBeAccessed(send);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user