You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
715 lines
24 KiB
715 lines
24 KiB
using AutoFixture.Xunit2; |
|
using Bit.Core.AdminConsole.Entities; |
|
using Bit.Core.Context; |
|
using Bit.Core.Enums; |
|
using Bit.Core.Repositories; |
|
using Bit.Core.Services; |
|
using Bit.Core.Vault.Entities; |
|
using Bit.Core.Vault.Models.Data; |
|
using Bit.Core.Vault.Repositories; |
|
using Bit.Events.Controllers; |
|
using Bit.Events.Models; |
|
using Microsoft.AspNetCore.Mvc; |
|
using NSubstitute; |
|
|
|
namespace Events.Test.Controllers; |
|
|
|
public class CollectControllerTests |
|
{ |
|
private readonly CollectController _sut; |
|
private readonly ICurrentContext _currentContext; |
|
private readonly IEventService _eventService; |
|
private readonly ICipherRepository _cipherRepository; |
|
private readonly IOrganizationRepository _organizationRepository; |
|
|
|
public CollectControllerTests() |
|
{ |
|
_currentContext = Substitute.For<ICurrentContext>(); |
|
_eventService = Substitute.For<IEventService>(); |
|
_cipherRepository = Substitute.For<ICipherRepository>(); |
|
_organizationRepository = Substitute.For<IOrganizationRepository>(); |
|
|
|
_sut = new CollectController( |
|
_currentContext, |
|
_eventService, |
|
_cipherRepository, |
|
_organizationRepository |
|
); |
|
} |
|
|
|
[Fact] |
|
public async Task Post_NullModel_ReturnsBadRequest() |
|
{ |
|
var result = await _sut.Post(null); |
|
|
|
Assert.IsType<BadRequestResult>(result); |
|
} |
|
|
|
[Fact] |
|
public async Task Post_EmptyModel_ReturnsBadRequest() |
|
{ |
|
var result = await _sut.Post(new List<EventModel>()); |
|
|
|
Assert.IsType<BadRequestResult>(result); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_UserClientExportedVault_LogsUserEvent(Guid userId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.User_ClientExportedVault, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogUserEventAsync(userId, EventType.User_ClientExportedVault, eventDate); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherAutofilled_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId, userId); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.Count() == 1 && |
|
tuples.First().Item1 == cipherDetails && |
|
tuples.First().Item2 == EventType.Cipher_ClientAutofilled && |
|
tuples.First().Item3 == eventDate |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientCopiedPassword_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientCopiedPassword, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientCopiedPassword |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientCopiedHiddenField_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientCopiedHiddenField, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientCopiedHiddenField |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientCopiedCardCode_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientCopiedCardCode, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientCopiedCardCode |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientToggledCardNumberVisible_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientToggledCardNumberVisible, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientToggledCardNumberVisible |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientToggledCardCodeVisible_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientToggledCardCodeVisible, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientToggledCardCodeVisible |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientToggledHiddenFieldVisible_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientToggledHiddenFieldVisible, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientToggledHiddenFieldVisible |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientToggledPasswordVisible_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientToggledPasswordVisible, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientToggledPasswordVisible |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherClientViewed_WithValidCipher_LogsCipherEvent(Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientViewed, |
|
CipherId = cipherId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item2 == EventType.Cipher_ClientViewed |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherEvent_WithoutCipherId_SkipsEvent(Guid userId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = null, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _cipherRepository.DidNotReceiveWithAnyArgs().GetByIdAsync(default, default); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherEvent_WithNullCipher_WithoutOrgId_SkipsEvent(Guid userId, Guid cipherId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns((CipherDetails?)null); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
OrganizationId = null, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId, userId); |
|
await _cipherRepository.DidNotReceiveWithAnyArgs().GetByIdAsync(cipherId); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherEvent_WithNullCipher_WithOrgId_ChecksOrgCipher( |
|
Guid userId, Guid cipherId, Guid orgId, Cipher cipher, CurrentContextOrganization org) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipher.Id = cipherId; |
|
cipher.OrganizationId = orgId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns((CipherDetails?)null); |
|
_cipherRepository.GetByIdAsync(cipherId).Returns(cipher); |
|
_currentContext.GetOrganization(orgId).Returns(org); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
OrganizationId = orgId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId, userId); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>( |
|
tuples => tuples.First().Item1 == cipher |
|
) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherEvent_WithNullCipher_OrgCipherNotFound_SkipsEvent( |
|
Guid userId, Guid cipherId, Guid orgId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns((CipherDetails?)null); |
|
_cipherRepository.GetByIdAsync(cipherId).Returns((CipherDetails?)null); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
OrganizationId = orgId, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId, userId); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherEvent_CipherDoesNotBelongToOrg_SkipsEvent( |
|
Guid userId, Guid cipherId, Guid orgId, Guid differentOrgId, Cipher cipher) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipher.Id = cipherId; |
|
cipher.OrganizationId = differentOrgId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns((CipherDetails?)null); |
|
_cipherRepository.GetByIdAsync(cipherId).Returns(cipher); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
OrganizationId = orgId, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_CipherEvent_OrgNotFound_SkipsEvent( |
|
Guid userId, Guid cipherId, Guid orgId, Cipher cipher) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipher.Id = cipherId; |
|
cipher.OrganizationId = orgId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns((CipherDetails?)null); |
|
_cipherRepository.GetByIdAsync(cipherId).Returns(cipher); |
|
_currentContext.GetOrganization(orgId).Returns((CurrentContextOrganization)null); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
OrganizationId = orgId, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_MultipleCipherEvents_WithSameCipherId_UsesCachedCipher( |
|
Guid userId, Guid cipherId, CipherDetails cipherDetails) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
Date = DateTime.UtcNow |
|
}, |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientViewed, |
|
CipherId = cipherId, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _cipherRepository.Received(1).GetByIdAsync(cipherId, userId); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>(tuples => tuples.Count() == 2) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_OrganizationClientExportedVault_WithValidOrg_LogsOrgEvent( |
|
Guid userId, Guid orgId, Organization organization) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
organization.Id = orgId; |
|
_organizationRepository.GetByIdAsync(orgId).Returns(organization); |
|
var eventDate = DateTime.UtcNow; |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Organization_ClientExportedVault, |
|
OrganizationId = orgId, |
|
Date = eventDate |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _organizationRepository.Received(1).GetByIdAsync(orgId); |
|
await _eventService.Received(1).LogOrganizationEventAsync(organization, EventType.Organization_ClientExportedVault, eventDate); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_OrganizationClientExportedVault_WithoutOrgId_SkipsEvent(Guid userId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Organization_ClientExportedVault, |
|
OrganizationId = null, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _organizationRepository.DidNotReceiveWithAnyArgs().GetByIdAsync(default); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogOrganizationEventAsync(default, default, default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_OrganizationClientExportedVault_WithNullOrg_SkipsEvent(Guid userId, Guid orgId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
_organizationRepository.GetByIdAsync(orgId).Returns((Organization)null); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.Organization_ClientExportedVault, |
|
OrganizationId = orgId, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _organizationRepository.Received(1).GetByIdAsync(orgId); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogOrganizationEventAsync(default, default, default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_UnsupportedEventType_SkipsEvent(Guid userId) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.User_LoggedIn, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.DidNotReceiveWithAnyArgs().LogUserEventAsync(default, default, default); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_MixedEventTypes_ProcessesAllEvents( |
|
Guid userId, Guid cipherId, Guid orgId, CipherDetails cipherDetails, Organization organization) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
cipherDetails.Id = cipherId; |
|
organization.Id = orgId; |
|
_cipherRepository.GetByIdAsync(cipherId, userId).Returns(cipherDetails); |
|
_organizationRepository.GetByIdAsync(orgId).Returns(organization); |
|
var events = new List<EventModel> |
|
{ |
|
new EventModel |
|
{ |
|
Type = EventType.User_ClientExportedVault, |
|
Date = DateTime.UtcNow |
|
}, |
|
new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipherId, |
|
Date = DateTime.UtcNow |
|
}, |
|
new EventModel |
|
{ |
|
Type = EventType.Organization_ClientExportedVault, |
|
OrganizationId = orgId, |
|
Date = DateTime.UtcNow |
|
} |
|
}; |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogUserEventAsync(userId, EventType.User_ClientExportedVault, Arg.Any<DateTime?>()); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>(tuples => tuples.Count() == 1) |
|
); |
|
await _eventService.Received(1).LogOrganizationEventAsync(organization, EventType.Organization_ClientExportedVault, Arg.Any<DateTime?>()); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_MoreThan50CipherEvents_LogsInBatches(Guid userId, List<CipherDetails> ciphers) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
var events = new List<EventModel>(); |
|
|
|
for (int i = 0; i < 100; i++) |
|
{ |
|
var cipher = ciphers[i % ciphers.Count]; |
|
_cipherRepository.GetByIdAsync(cipher.Id, userId).Returns(cipher); |
|
events.Add(new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipher.Id, |
|
Date = DateTime.UtcNow |
|
}); |
|
} |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(2).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>(tuples => tuples.Count() == 50) |
|
); |
|
} |
|
|
|
[Theory] |
|
[AutoData] |
|
public async Task Post_Exactly50CipherEvents_LogsInSingleBatch(Guid userId, List<CipherDetails> ciphers) |
|
{ |
|
_currentContext.UserId.Returns(userId); |
|
var events = new List<EventModel>(); |
|
|
|
for (int i = 0; i < 50; i++) |
|
{ |
|
var cipher = ciphers[i % ciphers.Count]; |
|
_cipherRepository.GetByIdAsync(cipher.Id, userId).Returns(cipher); |
|
events.Add(new EventModel |
|
{ |
|
Type = EventType.Cipher_ClientAutofilled, |
|
CipherId = cipher.Id, |
|
Date = DateTime.UtcNow |
|
}); |
|
} |
|
|
|
var result = await _sut.Post(events); |
|
|
|
Assert.IsType<OkResult>(result); |
|
await _eventService.Received(1).LogCipherEventsAsync( |
|
Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>(tuples => tuples.Count() == 50) |
|
); |
|
} |
|
}
|
|
|