Skip to content

Commit

Permalink
PM-15066 added drop feature and unit tests. (bitwarden#5053)
Browse files Browse the repository at this point in the history
  • Loading branch information
voommen-livefront authored Nov 20, 2024
1 parent 052235b commit 92b94fd
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 7 deletions.
28 changes: 26 additions & 2 deletions src/Api/Tools/Controllers/ReportsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Bit.Core.Tools.ReportFeatures.Interfaces;
using Bit.Core.Tools.ReportFeatures.OrganizationReportMembers.Interfaces;
using Bit.Core.Tools.ReportFeatures.Requests;
using Bit.Core.Tools.Requests;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -21,18 +20,21 @@ public class ReportsController : Controller
private readonly IMemberAccessCipherDetailsQuery _memberAccessCipherDetailsQuery;
private readonly IAddPasswordHealthReportApplicationCommand _addPwdHealthReportAppCommand;
private readonly IGetPasswordHealthReportApplicationQuery _getPwdHealthReportAppQuery;
private readonly IDropPasswordHealthReportApplicationCommand _dropPwdHealthReportAppCommand;

public ReportsController(
ICurrentContext currentContext,
IMemberAccessCipherDetailsQuery memberAccessCipherDetailsQuery,
IAddPasswordHealthReportApplicationCommand addPasswordHealthReportApplicationCommand,
IGetPasswordHealthReportApplicationQuery getPasswordHealthReportApplicationQuery
IGetPasswordHealthReportApplicationQuery getPasswordHealthReportApplicationQuery,
IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand
)
{
_currentContext = currentContext;
_memberAccessCipherDetailsQuery = memberAccessCipherDetailsQuery;
_addPwdHealthReportAppCommand = addPasswordHealthReportApplicationCommand;
_getPwdHealthReportAppQuery = getPasswordHealthReportApplicationQuery;
_dropPwdHealthReportAppCommand = dropPwdHealthReportAppCommand;
}

/// <summary>
Expand Down Expand Up @@ -161,4 +163,26 @@ public async Task<IEnumerable<PasswordHealthReportApplication>> AddPasswordHealt

return await _addPwdHealthReportAppCommand.AddPasswordHealthReportApplicationAsync(commandRequests);
}

/// <summary>
/// Drops a record from PasswordHealthReportApplication
/// </summary>
/// <param name="request">
/// A single instance of DropPasswordHealthReportApplicationRequest
/// { OrganizationId, array of PasswordHealthReportApplicationIds }
/// </param>
/// <returns></returns>
/// <exception cref="NotFoundException">If user does not have access to the organization</exception>
/// <exception cref="BadRequestException">If the organization does not have any records</exception>
[HttpDelete("password-health-report-application")]
public async Task DropPasswordHealthReportApplication(
[FromBody] DropPasswordHealthReportApplicationRequest request)
{
if (!await _currentContext.AccessReports(request.OrganizationId))
{
throw new NotFoundException();
}

await _dropPwdHealthReportAppCommand.DropPasswordHealthReportApplicationAsync(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
using Bit.Core.Repositories;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.ReportFeatures.Interfaces;
using Bit.Core.Tools.ReportFeatures.Requests;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.Requests;

namespace Bit.Core.Tools.ReportFeatures;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Bit.Core.Exceptions;
using Bit.Core.Tools.ReportFeatures.Interfaces;
using Bit.Core.Tools.ReportFeatures.Requests;
using Bit.Core.Tools.Repositories;

namespace Bit.Core.Tools.ReportFeatures;

public class DropPasswordHealthReportApplicationCommand : IDropPasswordHealthReportApplicationCommand
{
private IPasswordHealthReportApplicationRepository _passwordHealthReportApplicationRepo;

public DropPasswordHealthReportApplicationCommand(
IPasswordHealthReportApplicationRepository passwordHealthReportApplicationRepository)
{
_passwordHealthReportApplicationRepo = passwordHealthReportApplicationRepository;
}

public async Task DropPasswordHealthReportApplicationAsync(DropPasswordHealthReportApplicationRequest request)
{
var data = await _passwordHealthReportApplicationRepo.GetByOrganizationIdAsync(request.OrganizationId);
if (data == null)
{
throw new BadRequestException("Organization does not have any records.");
}

data.Where(_ => request.PasswordHealthReportApplicationIds.Contains(_.Id)).ToList().ForEach(async _ =>
{
await _passwordHealthReportApplicationRepo.DeleteAsync(_);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Requests;
using Bit.Core.Tools.ReportFeatures.Requests;

namespace Bit.Core.Tools.ReportFeatures.Interfaces;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Bit.Core.Tools.ReportFeatures.Requests;

namespace Bit.Core.Tools.ReportFeatures.Interfaces;

public interface IDropPasswordHealthReportApplicationCommand
{
Task DropPasswordHealthReportApplicationAsync(DropPasswordHealthReportApplicationRequest request);
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public static void AddReportingServices(this IServiceCollection services)
services.AddScoped<IMemberAccessCipherDetailsQuery, MemberAccessCipherDetailsQuery>();
services.AddScoped<IAddPasswordHealthReportApplicationCommand, AddPasswordHealthReportApplicationCommand>();
services.AddScoped<IGetPasswordHealthReportApplicationQuery, GetPasswordHealthReportApplicationQuery>();
services.AddScoped<IDropPasswordHealthReportApplicationCommand, DropPasswordHealthReportApplicationCommand>();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Bit.Core.Tools.Requests;
namespace Bit.Core.Tools.ReportFeatures.Requests;

public class AddPasswordHealthReportApplicationRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Bit.Core.Tools.ReportFeatures.Requests;

public class DropPasswordHealthReportApplicationRequest
{
public Guid OrganizationId { get; set; }
public IEnumerable<Guid> PasswordHealthReportApplicationIds { get; set; }
}
97 changes: 96 additions & 1 deletion test/Api.Test/Tools/Controllers/ReportsControllerTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Bit.Api.Tools.Controllers;
using AutoFixture;
using Bit.Api.Tools.Controllers;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Tools.ReportFeatures.Interfaces;
using Bit.Core.Tools.ReportFeatures.Requests;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
Expand Down Expand Up @@ -45,5 +47,98 @@ public async Task GetPasswordHealthReportApplicationAsync_withoutAccess(SutProvi
.Received(0);
}

[Theory, BitAutoData]
public async Task AddPasswordHealthReportApplicationAsync_withAccess_success(SutProvider<ReportsController> sutProvider)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);

// Act
var request = new Api.Tools.Models.PasswordHealthReportApplicationModel
{
OrganizationId = Guid.NewGuid(),
Url = "https://example.com",
};
await sutProvider.Sut.AddPasswordHealthReportApplication(request);

// Assert
_ = sutProvider.GetDependency<IAddPasswordHealthReportApplicationCommand>()
.Received(1)
.AddPasswordHealthReportApplicationAsync(Arg.Is<AddPasswordHealthReportApplicationRequest>(_ =>
_.OrganizationId == request.OrganizationId && _.Url == request.Url));
}

[Theory, BitAutoData]
public async Task AddPasswordHealthReportApplicationAsync_multiple_withAccess_success(
SutProvider<ReportsController> sutProvider)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);

// Act
var fixture = new Fixture();
var request = fixture.CreateMany<Api.Tools.Models.PasswordHealthReportApplicationModel>(2);
await sutProvider.Sut.AddPasswordHealthReportApplications(request);

// Assert
_ = sutProvider.GetDependency<IAddPasswordHealthReportApplicationCommand>()
.Received(1)
.AddPasswordHealthReportApplicationAsync(Arg.Any<IEnumerable<AddPasswordHealthReportApplicationRequest>>());
}

[Theory, BitAutoData]
public async Task AddPasswordHealthReportApplicationAsync_withoutAccess(SutProvider<ReportsController> sutProvider)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(false);

// Act
var request = new Api.Tools.Models.PasswordHealthReportApplicationModel
{
OrganizationId = Guid.NewGuid(),
Url = "https://example.com",
};
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.AddPasswordHealthReportApplication(request));

// Assert
_ = sutProvider.GetDependency<IAddPasswordHealthReportApplicationCommand>()
.Received(0);
}

[Theory, BitAutoData]
public async Task DropPasswordHealthReportApplicationAsync_withoutAccess(SutProvider<ReportsController> sutProvider)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(false);

// Act
var fixture = new Fixture();
var request = fixture.Create<Api.Tools.Models.PasswordHealthReportApplicationModel>();
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.AddPasswordHealthReportApplication(request));

// Assert
_ = sutProvider.GetDependency<IDropPasswordHealthReportApplicationCommand>()
.Received(0);
}

[Theory, BitAutoData]
public async Task DropPasswordHealthReportApplicationAsync_withAccess_success(SutProvider<ReportsController> sutProvider)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);

// Act
var fixture = new Fixture();
var request = fixture.Create<DropPasswordHealthReportApplicationRequest>();
await sutProvider.Sut.DropPasswordHealthReportApplication(request);

// Assert
_ = sutProvider.GetDependency<IDropPasswordHealthReportApplicationCommand>()
.Received(1)
.DropPasswordHealthReportApplicationAsync(Arg.Is<DropPasswordHealthReportApplicationRequest>(_ =>
_.OrganizationId == request.OrganizationId &&
_.PasswordHealthReportApplicationIds == request.PasswordHealthReportApplicationIds));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using Bit.Core.Repositories;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.ReportFeatures;
using Bit.Core.Tools.ReportFeatures.Requests;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.Requests;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using AutoFixture;
using Bit.Core.Exceptions;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.ReportFeatures;
using Bit.Core.Tools.ReportFeatures.Requests;
using Bit.Core.Tools.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;

namespace Bit.Core.Test.Tools.ReportFeatures;

[SutProviderCustomize]
public class DeletePasswordHealthReportApplicationCommandTests
{
[Theory, BitAutoData]
public async Task DropPasswordHealthReportApplicationAsync_withValidRequest_Success(
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var passwordHealthReportApplications = fixture.CreateMany<PasswordHealthReportApplication>(2).ToList();
// only take one id from the list - we only want to drop one record
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
.With(x => x.PasswordHealthReportApplicationIds,
passwordHealthReportApplications.Select(x => x.Id).Take(1).ToList())
.Create();

sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(passwordHealthReportApplications);

// Act
await sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request);

// Assert
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.Received(1)
.GetByOrganizationIdAsync(request.OrganizationId);

await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.Received(1)
.DeleteAsync(Arg.Is<PasswordHealthReportApplication>(_ =>
request.PasswordHealthReportApplicationIds.Contains(_.Id)));
}

[Theory, BitAutoData]
public async Task DropPasswordHealthReportApplicationAsync_withValidRequest_nothingToDrop(
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
var passwordHealthReportApplications = fixture.CreateMany<PasswordHealthReportApplication>(2).ToList();
// we are passing invalid data
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
.With(x => x.PasswordHealthReportApplicationIds, new List<Guid> { Guid.NewGuid() })
.Create();

sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(passwordHealthReportApplications);

// Act
await sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request);

// Assert
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.Received(1)
.GetByOrganizationIdAsync(request.OrganizationId);

await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.Received(0)
.DeleteAsync(Arg.Any<PasswordHealthReportApplication>());
}

[Theory, BitAutoData]
public async Task DropPasswordHealthReportApplicationAsync_withNodata_fails(
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
{
// Arrange
var fixture = new Fixture();
// we are passing invalid data
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
.Create();

sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.GetByOrganizationIdAsync(Arg.Any<Guid>())
.Returns(null as List<PasswordHealthReportApplication>);

// Act
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request));

// Assert
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.Received(1)
.GetByOrganizationIdAsync(request.OrganizationId);

await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
.Received(0)
.DeleteAsync(Arg.Any<PasswordHealthReportApplication>());
}
}

0 comments on commit 92b94fd

Please sign in to comment.