1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[PM-3581] Fix Postgres Time (#3221)

* Fix Postgres Time

- Migrate Send Tests
- Delete Old Tests

* Formatting

* Update Comment

* Change LaxComparer to Compare Some Milliseconds

* Update Comment
This commit is contained in:
Justin Baur 2024-06-28 10:13:02 -04:00 committed by GitHub
parent 48430836b6
commit 1ec2aae723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 115 additions and 169 deletions

View File

@ -159,6 +159,20 @@ public class DatabaseContext : DbContext
// Make sure this is called after configuring all the entities as it iterates through all setup entities. // Make sure this is called after configuring all the entities as it iterates through all setup entities.
private void ConfigureDateTimeUtcQueries(ModelBuilder builder) private void ConfigureDateTimeUtcQueries(ModelBuilder builder)
{ {
ValueConverter<DateTime, DateTime> converter;
if (Database.IsNpgsql())
{
converter = new ValueConverter<DateTime, DateTime>(
v => v,
d => new DateTimeOffset(d).UtcDateTime);
}
else
{
converter = new ValueConverter<DateTime, DateTime>(
v => v,
v => new DateTime(v.Ticks, DateTimeKind.Utc));
}
foreach (var entityType in builder.Model.GetEntityTypes()) foreach (var entityType in builder.Model.GetEntityTypes())
{ {
if (entityType.IsKeyless) if (entityType.IsKeyless)
@ -169,10 +183,7 @@ public class DatabaseContext : DbContext
{ {
if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
{ {
property.SetValueConverter( property.SetValueConverter(converter);
new ValueConverter<DateTime, DateTime>(
v => v,
v => new DateTime(v.Ticks, DateTimeKind.Utc)));
} }
} }
} }

View File

@ -1,71 +0,0 @@
using AutoFixture;
using AutoFixture.Kernel;
using Bit.Core.Test.AutoFixture.UserFixtures;
using Bit.Core.Tools.Entities;
using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
using Bit.Infrastructure.EFIntegration.Test.AutoFixture.Relays;
using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.Tools.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Infrastructure.EFIntegration.Test.Tools.AutoFixture;
internal class SendBuilder : ISpecimenBuilder
{
public bool OrganizationOwned { get; set; }
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var type = request as Type;
if (type == null || type != typeof(Send))
{
return new NoSpecimen();
}
var fixture = new Fixture();
fixture.Customizations.Insert(0, new MaxLengthStringRelay());
if (!OrganizationOwned)
{
fixture.Customize<Send>(composer => composer
.Without(c => c.OrganizationId));
}
var obj = fixture.WithAutoNSubstitutions().Create<Send>();
return obj;
}
}
internal class EfSend : ICustomization
{
public bool OrganizationOwned { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
fixture.Customizations.Add(new GlobalSettingsBuilder());
fixture.Customizations.Add(new SendBuilder());
fixture.Customizations.Add(new UserBuilder());
fixture.Customizations.Add(new OrganizationBuilder());
fixture.Customizations.Add(new EfRepositoryListBuilder<SendRepository>());
fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>());
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
}
}
internal class EfUserSendAutoDataAttribute : CustomAutoDataAttribute
{
public EfUserSendAutoDataAttribute() : base(new SutProviderCustomization(), new EfSend())
{ }
}
internal class EfOrganizationSendAutoDataAttribute : CustomAutoDataAttribute
{
public EfOrganizationSendAutoDataAttribute() : base(new SutProviderCustomization(), new EfSend()
{
OrganizationOwned = true,
})
{ }
}

View File

@ -1,26 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Bit.Core.Tools.Entities;
namespace Bit.Infrastructure.EFIntegration.Test.Tools.Repositories.EqualityComparers;
public class SendCompare : IEqualityComparer<Send>
{
public bool Equals(Send x, Send y)
{
return x.Type == y.Type &&
x.Data == y.Data &&
x.Key == y.Key &&
x.Password == y.Password &&
x.MaxAccessCount == y.MaxAccessCount &&
x.AccessCount == y.AccessCount &&
x.ExpirationDate?.ToShortDateString() == y.ExpirationDate?.ToShortDateString() &&
x.DeletionDate.ToShortDateString() == y.DeletionDate.ToShortDateString() &&
x.Disabled == y.Disabled &&
x.HideEmail == y.HideEmail;
}
public int GetHashCode([DisallowNull] Send obj)
{
return base.GetHashCode();
}
}

View File

@ -1,68 +0,0 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Entities;
using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Core.Tools.Entities;
using Bit.Infrastructure.EFIntegration.Test.Tools.AutoFixture;
using Bit.Infrastructure.EFIntegration.Test.Tools.Repositories.EqualityComparers;
using Xunit;
using EfRepo = Bit.Infrastructure.EntityFramework.Repositories;
using EfSendRepo = Bit.Infrastructure.EntityFramework.Tools.Repositories;
using SqlRepo = Bit.Infrastructure.Dapper.Repositories;
using SqlSendRepo = Bit.Infrastructure.Dapper.Tools.Repositories;
namespace Bit.Infrastructure.EFIntegration.Test.Tools.Repositories;
public class SendRepositoryTests
{
[CiSkippedTheory, EfUserSendAutoData, EfOrganizationSendAutoData]
public async Task CreateAsync_Works_DataMatches(
Send send,
User user,
Organization org,
SendCompare equalityComparer,
List<EfSendRepo.SendRepository> suts,
List<EfRepo.UserRepository> efUserRepos,
List<EfRepo.OrganizationRepository> efOrgRepos,
SqlSendRepo.SendRepository sqlSendRepo,
SqlRepo.UserRepository sqlUserRepo,
SqlRepo.OrganizationRepository sqlOrgRepo
)
{
var savedSends = new List<Send>();
foreach (var sut in suts)
{
var i = suts.IndexOf(sut);
if (send.OrganizationId.HasValue)
{
var efOrg = await efOrgRepos[i].CreateAsync(org);
sut.ClearChangeTracking();
send.OrganizationId = efOrg.Id;
}
var efUser = await efUserRepos[i].CreateAsync(user);
sut.ClearChangeTracking();
send.UserId = efUser.Id;
var postEfSend = await sut.CreateAsync(send);
sut.ClearChangeTracking();
var savedSend = await sut.GetByIdAsync(postEfSend.Id);
savedSends.Add(savedSend);
}
var sqlUser = await sqlUserRepo.CreateAsync(user);
if (send.OrganizationId.HasValue)
{
var sqlOrg = await sqlOrgRepo.CreateAsync(org);
send.OrganizationId = sqlOrg.Id;
}
send.UserId = sqlUser.Id;
var sqlSend = await sqlSendRepo.CreateAsync(send);
var savedSqlSend = await sqlSendRepo.GetByIdAsync(sqlSend.Id);
savedSends.Add(savedSqlSend);
var distinctItems = savedSends.Distinct(equalityComparer);
Assert.True(!distinctItems.Skip(1).Any());
}
}

View File

@ -0,0 +1,34 @@
using System.Diagnostics.CodeAnalysis;
namespace Bit.Infrastructure.IntegrationTest.Comparers;
/// <summary>
/// A datetime comparer that doesn't care about overall ticks and instead allows a configurable allowed difference.
/// </summary>
public class LaxDateTimeComparer : IEqualityComparer<DateTime>
{
public static readonly IEqualityComparer<DateTime> Default = new LaxDateTimeComparer(TimeSpan.FromMilliseconds(2));
private readonly TimeSpan _allowedDifference;
public LaxDateTimeComparer(TimeSpan allowedDifference)
{
_allowedDifference = allowedDifference;
}
public bool Equals(DateTime x, DateTime y)
{
var difference = x - y;
return difference.Duration() < _allowedDifference;
}
public int GetHashCode([DisallowNull] DateTime obj)
{
// Not used when used for Assert.Equal() overload
throw new NotImplementedException();
}
public static int RoundMilliseconds(int milliseconds)
{
return (int)Math.Round(milliseconds / 100d) * 100;
}
}

View File

@ -0,0 +1,66 @@
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Repositories;
using Bit.Infrastructure.IntegrationTest.Comparers;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.Tools;
public class SendRepositoryTests
{
[DatabaseTheory, DatabaseData]
public async Task CreateAsync_Works(ISendRepository sendRepository)
{
var expirationDate = DateTime.UtcNow.AddDays(7);
var createdSend = await sendRepository.CreateAsync(new Send
{
Data = "{\"Text\": \"2.t|t|t\"}", // TODO: EF Should enforce this
Type = SendType.Text,
AccessCount = 0,
Key = "2.t|t|t", // TODO: EF should enforce this
ExpirationDate = expirationDate,
DeletionDate = expirationDate.AddDays(7),
});
Assert.NotNull(createdSend.ExpirationDate);
Assert.Equal(expirationDate, createdSend.ExpirationDate!.Value, LaxDateTimeComparer.Default);
var sendFromDatabase = await sendRepository.GetByIdAsync(createdSend.Id);
Assert.Equal(expirationDate, sendFromDatabase.ExpirationDate!.Value, LaxDateTimeComparer.Default);
Assert.Equal(SendType.Text, sendFromDatabase.Type);
Assert.Equal(0, sendFromDatabase.AccessCount);
Assert.Equal("2.t|t|t", sendFromDatabase.Key);
Assert.Equal(expirationDate.AddDays(7), sendFromDatabase.DeletionDate, LaxDateTimeComparer.Default);
Assert.Equal("{\"Text\": \"2.t|t|t\"}", sendFromDatabase.Data);
}
[DatabaseTheory, DatabaseData]
// This test runs best on a fresh database and may fail on subsequent runs with other tests.
public async Task GetByDeletionDateAsync_Works(ISendRepository sendRepository)
{
var deletionDate = DateTime.UtcNow.AddYears(-1);
var shouldDeleteSend = await sendRepository.CreateAsync(new Send
{
Data = "{\"Text\": \"2.t|t|t\"}", // TODO: EF Should enforce this
Type = SendType.Text,
AccessCount = 0,
Key = "2.t|t|t", // TODO: EF should enforce this
DeletionDate = deletionDate.AddSeconds(-2),
});
var shouldKeepSend = await sendRepository.CreateAsync(new Send
{
Data = "{\"Text\": \"2.t|t|t\"}", // TODO: EF Should enforce this
Type = SendType.Text,
AccessCount = 0,
Key = "2.t|t|t", // TODO: EF should enforce this
DeletionDate = deletionDate.AddSeconds(2),
});
var toDeleteSends = await sendRepository.GetManyByDeletionDateAsync(deletionDate);
var toDeleteSend = Assert.Single(toDeleteSends);
Assert.Equal(shouldDeleteSend.Id, toDeleteSend.Id);
}
}