mirror of
https://github.com/bitwarden/server
synced 2026-01-13 05:53:37 +00:00
introduce tools-send-email-otp-listing feature flag
This commit is contained in:
@@ -16,6 +16,7 @@ using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures;
|
||||
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
|
||||
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -33,6 +34,9 @@ public class SendsController : Controller
|
||||
private readonly ISendFileStorageService _sendFileStorageService;
|
||||
private readonly IAnonymousSendCommand _anonymousSendCommand;
|
||||
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
|
||||
|
||||
private readonly ISendOwnerQuery _sendOwnerQuery;
|
||||
|
||||
private readonly ILogger<SendsController> _logger;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
@@ -42,6 +46,7 @@ public class SendsController : Controller
|
||||
ISendAuthorizationService sendAuthorizationService,
|
||||
IAnonymousSendCommand anonymousSendCommand,
|
||||
INonAnonymousSendCommand nonAnonymousSendCommand,
|
||||
ISendOwnerQuery sendOwnerQuery,
|
||||
ISendFileStorageService sendFileStorageService,
|
||||
ILogger<SendsController> logger,
|
||||
GlobalSettings globalSettings)
|
||||
@@ -51,6 +56,7 @@ public class SendsController : Controller
|
||||
_sendAuthorizationService = sendAuthorizationService;
|
||||
_anonymousSendCommand = anonymousSendCommand;
|
||||
_nonAnonymousSendCommand = nonAnonymousSendCommand;
|
||||
_sendOwnerQuery = sendOwnerQuery;
|
||||
_sendFileStorageService = sendFileStorageService;
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
@@ -181,23 +187,19 @@ public class SendsController : Controller
|
||||
[HttpGet("{id}")]
|
||||
public async Task<SendResponseModel> Get(string id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var send = await _sendRepository.GetByIdAsync(new Guid(id));
|
||||
if (send == null || send.UserId != userId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new SendResponseModel(send, _globalSettings);
|
||||
var sendId = new Guid(id);
|
||||
var send = await _sendOwnerQuery.Get(sendId, User);
|
||||
return new SendResponseModel(send);
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<SendResponseModel>> Get()
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var sends = await _sendRepository.GetManyByUserIdAsync(userId);
|
||||
var responses = sends.Select(s => new SendResponseModel(s, _globalSettings));
|
||||
return new ListResponseModel<SendResponseModel>(responses);
|
||||
var sends = await _sendOwnerQuery.GetOwned(User);
|
||||
var responses = sends.Select(s => new SendResponseModel(s));
|
||||
var result = new ListResponseModel<SendResponseModel>(responses);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
@@ -207,7 +209,7 @@ public class SendsController : Controller
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var send = model.ToSend(userId, _sendAuthorizationService);
|
||||
await _nonAnonymousSendCommand.SaveSendAsync(send);
|
||||
return new SendResponseModel(send, _globalSettings);
|
||||
return new SendResponseModel(send);
|
||||
}
|
||||
|
||||
[HttpPost("file/v2")]
|
||||
@@ -236,7 +238,7 @@ public class SendsController : Controller
|
||||
{
|
||||
Url = uploadUrl,
|
||||
FileUploadType = _sendFileStorageService.FileUploadType,
|
||||
SendResponse = new SendResponseModel(send, _globalSettings)
|
||||
SendResponse = new SendResponseModel(send)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,7 +262,7 @@ public class SendsController : Controller
|
||||
{
|
||||
Url = await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId),
|
||||
FileUploadType = _sendFileStorageService.FileUploadType,
|
||||
SendResponse = new SendResponseModel(send, _globalSettings),
|
||||
SendResponse = new SendResponseModel(send),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -294,7 +296,7 @@ public class SendsController : Controller
|
||||
}
|
||||
|
||||
await _nonAnonymousSendCommand.SaveSendAsync(model.ToSend(send, _sendAuthorizationService));
|
||||
return new SendResponseModel(send, _globalSettings);
|
||||
return new SendResponseModel(send);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/remove-password")]
|
||||
@@ -309,7 +311,7 @@ public class SendsController : Controller
|
||||
|
||||
send.Password = null;
|
||||
await _nonAnonymousSendCommand.SaveSendAsync(send);
|
||||
return new SendResponseModel(send, _globalSettings);
|
||||
return new SendResponseModel(send);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Data;
|
||||
@@ -21,17 +20,13 @@ public class SendResponseModel : ResponseModel
|
||||
/// Instantiates a send response model
|
||||
/// </summary>
|
||||
/// <param name="send">Content to transmit to the client.</param>
|
||||
/// <param name="globalSettings">
|
||||
/// Settings that control response generation.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="send"/> is <see langword="null" />
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="send" /> has an invalid <see cref="Send.Type"/>.
|
||||
/// </exception>
|
||||
// FIXME: remove `globalSettings` variable
|
||||
public SendResponseModel(Send send, GlobalSettings globalSettings)
|
||||
public SendResponseModel(Send send)
|
||||
: base("send")
|
||||
{
|
||||
if (send == null)
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SyncResponseModel() : ResponseModel("sync")
|
||||
c => new CollectionDetailsResponseModel(c)) ?? new List<CollectionDetailsResponseModel>();
|
||||
Domains = excludeDomains ? null : new DomainsResponseModel(user, false);
|
||||
Policies = policies?.Select(p => new PolicyResponseModel(p)) ?? new List<PolicyResponseModel>();
|
||||
Sends = sends.Select(s => new SendResponseModel(s, globalSettings));
|
||||
Sends = sends.Select(s => new SendResponseModel(s));
|
||||
UserDecryption = new UserDecryptionResponseModel
|
||||
{
|
||||
MasterPasswordUnlock = user.HasMasterPassword()
|
||||
|
||||
@@ -200,10 +200,24 @@ public static class FeatureFlagKeys
|
||||
public const string PushNotificationsWhenLocked = "pm-19388-push-notifications-when-locked";
|
||||
|
||||
/* Tools Team */
|
||||
/// <summary>
|
||||
/// Enable this flag to share the send view used by the web and browser clients
|
||||
/// on the desktop client.
|
||||
/// </summary>
|
||||
public const string DesktopSendUIRefresh = "desktop-send-ui-refresh";
|
||||
public const string UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators";
|
||||
public const string UseChromiumImporter = "pm-23982-chromium-importer";
|
||||
|
||||
/// <summary>
|
||||
/// Enable this flag to output email/OTP authenticated sends from the `GET sends` endpoint. When
|
||||
/// this flag is disabled, the `GET sends` endpoint omits email/OTP authenticated sends.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This flag is server-side only, and only inhibits the endpoint returning all sends.
|
||||
/// Email/OTP sends can still be created and downloaded through other endpoints.
|
||||
/// </remarks>
|
||||
public const string PM19051_ListEmailOtpSends = "tools-send-email-otp-listing";
|
||||
|
||||
/* Vault Team */
|
||||
public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge";
|
||||
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.Entities;
|
||||
|
||||
namespace Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Queries sends owned by the current user.
|
||||
/// </summary>
|
||||
public interface ISendOwnerQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a send.
|
||||
/// </summary>
|
||||
/// <param name="id">Identifies the send</param>
|
||||
/// <param name="user">The principal requesting the send.</param>
|
||||
/// <returns>The send</returns>
|
||||
/// <exception cref="NotFoundException">
|
||||
/// Thrown when <paramref name="id"/> fails to identify a send
|
||||
/// owned by the user.
|
||||
/// </exception>
|
||||
/// <exception cref="BadRequestException">
|
||||
/// Thrown when the query cannot identify the current user.
|
||||
/// </exception>
|
||||
Task<Send> Get(Guid id, ClaimsPrincipal user);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all sends owned by the current user.
|
||||
/// </summary>
|
||||
/// <param name="user">The principal requesting the send.</param>
|
||||
/// <returns>
|
||||
/// A sequence of all owned sends.
|
||||
/// </returns>
|
||||
/// <exception cref="BadRequestException">
|
||||
/// Thrown when the query cannot identify the current user.
|
||||
/// </exception>
|
||||
Task<ICollection<Send>> GetOwned(ClaimsPrincipal user);
|
||||
}
|
||||
66
src/Core/Tools/SendFeatures/Queries/SendOwnerQuery.cs
Normal file
66
src/Core/Tools/SendFeatures/Queries/SendOwnerQuery.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
||||
|
||||
namespace Bit.Core.Tools.SendFeatures.Queries;
|
||||
|
||||
/// <inheritdoc cref="ISendOwnerQuery"/>
|
||||
public class SendOwnerQuery : ISendOwnerQuery
|
||||
{
|
||||
private readonly ISendRepository _repository;
|
||||
private readonly IFeatureService _features;
|
||||
private readonly IUserService _users;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates the command
|
||||
/// </summary>
|
||||
/// <param name="sendRepository">
|
||||
/// Retrieves send records
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="sendRepository"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
public SendOwnerQuery(ISendRepository sendRepository, IFeatureService features, IUserService users)
|
||||
{
|
||||
_repository = sendRepository;
|
||||
_features = features ?? throw new ArgumentNullException(nameof(features));
|
||||
_users = users ?? throw new ArgumentNullException(nameof(users));
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ISendOwnerQuery.Get"/>
|
||||
public async Task<Send> Get(Guid id, ClaimsPrincipal user)
|
||||
{
|
||||
var userId = _users.GetProperUserId(user) ?? throw new BadRequestException("invalid user.");
|
||||
var send = await _repository.GetByIdAsync(id);
|
||||
|
||||
if (send == null || send.UserId != userId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return send;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ISendOwnerQuery.GetOwned"/>
|
||||
public async Task<ICollection<Send>> GetOwned(ClaimsPrincipal user)
|
||||
{
|
||||
var userId = _users.GetProperUserId(user) ?? throw new BadRequestException("invalid user.");
|
||||
var sends = await _repository.GetManyByUserIdAsync(userId);
|
||||
|
||||
var removeEmailOtp = !_features.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends);
|
||||
if (removeEmailOtp)
|
||||
{
|
||||
// reify list to avoid invalidating the enumerator
|
||||
foreach (var s in sends.Where(s => s.Emails != null).ToList())
|
||||
{
|
||||
sends.Remove(s);
|
||||
}
|
||||
}
|
||||
|
||||
return sends;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.Tools.Controllers;
|
||||
using Bit.Api.Tools.Models.Request;
|
||||
using Bit.Api.Tools.Models.Response;
|
||||
@@ -12,6 +14,7 @@ using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
|
||||
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -29,6 +32,7 @@ public class SendsControllerTests : IDisposable
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
|
||||
private readonly IAnonymousSendCommand _anonymousSendCommand;
|
||||
private readonly ISendOwnerQuery _sendOwnerQuery;
|
||||
private readonly ISendAuthorizationService _sendAuthorizationService;
|
||||
private readonly ISendFileStorageService _sendFileStorageService;
|
||||
private readonly ILogger<SendsController> _logger;
|
||||
@@ -39,6 +43,7 @@ public class SendsControllerTests : IDisposable
|
||||
_sendRepository = Substitute.For<ISendRepository>();
|
||||
_nonAnonymousSendCommand = Substitute.For<INonAnonymousSendCommand>();
|
||||
_anonymousSendCommand = Substitute.For<IAnonymousSendCommand>();
|
||||
_sendOwnerQuery = Substitute.For<ISendOwnerQuery>();
|
||||
_sendAuthorizationService = Substitute.For<ISendAuthorizationService>();
|
||||
_sendFileStorageService = Substitute.For<ISendFileStorageService>();
|
||||
_globalSettings = new GlobalSettings();
|
||||
@@ -50,6 +55,7 @@ public class SendsControllerTests : IDisposable
|
||||
_sendAuthorizationService,
|
||||
_anonymousSendCommand,
|
||||
_nonAnonymousSendCommand,
|
||||
_sendOwnerQuery,
|
||||
_sendFileStorageService,
|
||||
_logger,
|
||||
_globalSettings
|
||||
@@ -109,4 +115,62 @@ public class SendsControllerTests : IDisposable
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostFile(request));
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task Get_WithValidId_ReturnsSendResponseModel(Guid sendId, Send send)
|
||||
{
|
||||
send.Type = SendType.Text;
|
||||
var textData = new SendTextData("Test Send", "Notes", "Sample text", false);
|
||||
send.Data = JsonSerializer.Serialize(textData);
|
||||
_sendOwnerQuery.Get(sendId, Arg.Any<ClaimsPrincipal>()).Returns(send);
|
||||
|
||||
var result = await _sut.Get(sendId.ToString());
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<SendResponseModel>(result);
|
||||
Assert.Equal(send.Id, result.Id);
|
||||
await _sendOwnerQuery.Received(1).Get(sendId, Arg.Any<ClaimsPrincipal>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task Get_WithInvalidGuid_ThrowsException(string invalidId)
|
||||
{
|
||||
await Assert.ThrowsAsync<FormatException>(() => _sut.Get(invalidId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllOwned_ReturnsListResponseModelWithSendResponseModels()
|
||||
{
|
||||
var textSendData = new SendTextData("Test Send 1", "Notes 1", "Sample text", false);
|
||||
var fileSendData = new SendFileData("Test Send 2", "Notes 2", "test.txt") { Id = "file-123", Size = 1024 };
|
||||
var sends = new List<Send>
|
||||
{
|
||||
new Send { Id = Guid.NewGuid(), Type = SendType.Text, Data = JsonSerializer.Serialize(textSendData) },
|
||||
new Send { Id = Guid.NewGuid(), Type = SendType.File, Data = JsonSerializer.Serialize(fileSendData) }
|
||||
};
|
||||
_sendOwnerQuery.GetOwned(Arg.Any<ClaimsPrincipal>()).Returns(sends);
|
||||
|
||||
var result = await _sut.Get();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<ListResponseModel<SendResponseModel>>(result);
|
||||
Assert.Equal(2, result.Data.Count());
|
||||
var sendResponseModels = result.Data.ToList();
|
||||
Assert.Equal(sends[0].Id, sendResponseModels[0].Id);
|
||||
Assert.Equal(sends[1].Id, sendResponseModels[1].Id);
|
||||
await _sendOwnerQuery.Received(1).GetOwned(Arg.Any<ClaimsPrincipal>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAllOwned_WhenNoSends_ReturnsEmptyListResponseModel()
|
||||
{
|
||||
_sendOwnerQuery.GetOwned(Arg.Any<ClaimsPrincipal>()).Returns(new List<Send>());
|
||||
|
||||
var result = await _sut.Get();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<ListResponseModel<SendResponseModel>>(result);
|
||||
Assert.Empty(result.Data);
|
||||
await _sendOwnerQuery.Received(1).GetOwned(Arg.Any<ClaimsPrincipal>());
|
||||
}
|
||||
}
|
||||
|
||||
169
test/Core.Test/Tools/Services/SendOwnerQueryTests.cs
Normal file
169
test/Core.Test/Tools/Services/SendOwnerQueryTests.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures.Queries;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Tools.Services;
|
||||
|
||||
public class SendOwnerQueryTests
|
||||
{
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly SendOwnerQuery _sendOwnerQuery;
|
||||
private readonly Guid _currentUserId = Guid.NewGuid();
|
||||
private readonly ClaimsPrincipal _user;
|
||||
|
||||
public SendOwnerQueryTests()
|
||||
{
|
||||
_sendRepository = Substitute.For<ISendRepository>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_userService = Substitute.For<IUserService>();
|
||||
_user = new ClaimsPrincipal();
|
||||
_userService.GetProperUserId(_user).Returns(_currentUserId);
|
||||
_sendOwnerQuery = new SendOwnerQuery(_sendRepository, _featureService, _userService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_WithValidSendOwnedByUser_ReturnsExpectedSend()
|
||||
{
|
||||
// Arrange
|
||||
var sendId = Guid.NewGuid();
|
||||
var expectedSend = CreateSend(sendId, _currentUserId);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(expectedSend);
|
||||
|
||||
// Act
|
||||
var result = await _sendOwnerQuery.Get(sendId, _user);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expectedSend, result);
|
||||
await _sendRepository.Received(1).GetByIdAsync(sendId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_WithNonExistentSend_ThrowsNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var sendId = Guid.NewGuid();
|
||||
_sendRepository.GetByIdAsync(sendId).Returns((Send?)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sendOwnerQuery.Get(sendId, _user));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_WithSendOwnedByDifferentUser_ThrowsNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var sendId = Guid.NewGuid();
|
||||
var differentUserId = Guid.NewGuid();
|
||||
var send = CreateSend(sendId, differentUserId);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => _sendOwnerQuery.Get(sendId, _user));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_WithNullCurrentUserId_ThrowsBadRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var sendId = Guid.NewGuid();
|
||||
var send = CreateSend(sendId, _currentUserId);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
var nullUser = new ClaimsPrincipal();
|
||||
_userService.GetProperUserId(nullUser).Returns((Guid?)null);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sendOwnerQuery.Get(sendId, nullUser));
|
||||
Assert.Equal("invalid user.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOwned_WithFeatureFlagEnabled_ReturnsAllSends()
|
||||
{
|
||||
// Arrange
|
||||
var sends = new List<Send>
|
||||
{
|
||||
CreateSend(Guid.NewGuid(), _currentUserId, emails: null),
|
||||
CreateSend(Guid.NewGuid(), _currentUserId, emails: "test@example.com"),
|
||||
CreateSend(Guid.NewGuid(), _currentUserId, emails: "other@example.com")
|
||||
};
|
||||
_sendRepository.GetManyByUserIdAsync(_currentUserId).Returns(sends);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends).Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await _sendOwnerQuery.GetOwned(_user);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.Contains(sends[0], result);
|
||||
Assert.Contains(sends[1], result);
|
||||
Assert.Contains(sends[2], result);
|
||||
await _sendRepository.Received(1).GetManyByUserIdAsync(_currentUserId);
|
||||
_featureService.Received(1).IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOwned_WithFeatureFlagDisabled_FiltersOutEmailOtpSends()
|
||||
{
|
||||
// Arrange
|
||||
var sendWithoutEmails = CreateSend(Guid.NewGuid(), _currentUserId, emails: null);
|
||||
var sendWithEmails = CreateSend(Guid.NewGuid(), _currentUserId, emails: "test@example.com");
|
||||
var sends = new List<Send> { sendWithoutEmails, sendWithEmails };
|
||||
_sendRepository.GetManyByUserIdAsync(_currentUserId).Returns(sends);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await _sendOwnerQuery.GetOwned(_user);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Contains(sendWithoutEmails, result);
|
||||
Assert.DoesNotContain(sendWithEmails, result);
|
||||
await _sendRepository.Received(1).GetManyByUserIdAsync(_currentUserId);
|
||||
_featureService.Received(1).IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOwned_WithNullCurrentUserId_ThrowsBadRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var nullUser = new ClaimsPrincipal();
|
||||
_userService.GetProperUserId(nullUser).Returns((Guid?)null);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sendOwnerQuery.GetOwned(nullUser));
|
||||
Assert.Equal("invalid user.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOwned_WithEmptyCollection_ReturnsEmptyCollection()
|
||||
{
|
||||
// Arrange
|
||||
var emptySends = new List<Send>();
|
||||
_sendRepository.GetManyByUserIdAsync(_currentUserId).Returns(emptySends);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends).Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await _sendOwnerQuery.GetOwned(_user);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
await _sendRepository.Received(1).GetManyByUserIdAsync(_currentUserId);
|
||||
}
|
||||
|
||||
private static Send CreateSend(Guid id, Guid userId, string? emails = null)
|
||||
{
|
||||
return new Send
|
||||
{
|
||||
Id = id,
|
||||
UserId = userId,
|
||||
Emails = emails
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user