Skip to content

Commit cd9310f

Browse files
author
Bart Koelman
committed
Fix failing tests when EF Core change tracker is purged more often (see next commit)
1 parent 9720ea0 commit cd9310f

File tree

10 files changed

+60
-21
lines changed

10 files changed

+60
-21
lines changed

test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionFakers.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@ internal sealed class TelevisionFakers : FakerContainer
2323
new Faker<TelevisionBroadcast>()
2424
.UseSeed(GetFakerSeed())
2525
.RuleFor(broadcast => broadcast.Title, faker => faker.Lorem.Sentence())
26-
.RuleFor(broadcast => broadcast.AiredAt, faker => faker.Date.PastOffset())
27-
.RuleFor(broadcast => broadcast.ArchivedAt, faker => faker.Date.RecentOffset()));
26+
.RuleFor(broadcast => broadcast.AiredAt, faker => faker.Date.PastOffset()
27+
.TruncateToWholeMilliseconds())
28+
.RuleFor(broadcast => broadcast.ArchivedAt, faker => faker.Date.RecentOffset()
29+
.TruncateToWholeMilliseconds()));
2830

2931
private readonly Lazy<Faker<BroadcastComment>> _lazyBroadcastCommentFaker = new(() =>
3032
new Faker<BroadcastComment>()
3133
.UseSeed(GetFakerSeed())
3234
.RuleFor(comment => comment.Text, faker => faker.Lorem.Paragraph())
33-
.RuleFor(comment => comment.CreatedAt, faker => faker.Date.PastOffset()));
35+
.RuleFor(comment => comment.CreatedAt, faker => faker.Date.PastOffset()
36+
.TruncateToWholeMilliseconds()));
3437

3538
public Faker<TelevisionNetwork> TelevisionNetwork => _lazyTelevisionNetworkFaker.Value;
3639
public Faker<TelevisionStation> TelevisionStation => _lazyTelevisionStationFaker.Value;

test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ public async Task Can_create_resource_with_client_generated_string_ID_having_no_
105105
attributes = new
106106
{
107107
title = newTrack.Title,
108-
lengthInSeconds = newTrack.LengthInSeconds
108+
lengthInSeconds = newTrack.LengthInSeconds,
109+
releasedAt = newTrack.ReleasedAt
109110
}
110111
}
111112
}

test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ internal sealed class OperationsFakers : FakerContainer
3030
.RuleFor(musicTrack => musicTrack.Title, faker => faker.Lorem.Word())
3131
.RuleFor(musicTrack => musicTrack.LengthInSeconds, faker => faker.Random.Decimal(3 * 60, 5 * 60))
3232
.RuleFor(musicTrack => musicTrack.Genre, faker => faker.Lorem.Word())
33-
.RuleFor(musicTrack => musicTrack.ReleasedAt, faker => faker.Date.PastOffset()));
33+
.RuleFor(musicTrack => musicTrack.ReleasedAt, faker => faker.Date.PastOffset()
34+
.TruncateToWholeMilliseconds()));
3435

3536
private readonly Lazy<Faker<Lyric>> _lazyLyricFaker = new(() =>
3637
new Faker<Lyric>()
@@ -47,7 +48,8 @@ internal sealed class OperationsFakers : FakerContainer
4748
new Faker<Performer>()
4849
.UseSeed(GetFakerSeed())
4950
.RuleFor(performer => performer.ArtistName, faker => faker.Name.FullName())
50-
.RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset()));
51+
.RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset()
52+
.TruncateToWholeMilliseconds()));
5153

5254
private readonly Lazy<Faker<RecordCompany>> _lazyRecordCompanyFaker = new(() =>
5355
new Faker<RecordCompany>()

test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditFakers.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ internal sealed class AuditFakers : FakerContainer
1313
new Faker<AuditEntry>()
1414
.UseSeed(GetFakerSeed())
1515
.RuleFor(auditEntry => auditEntry.UserName, faker => faker.Internet.UserName())
16-
.RuleFor(auditEntry => auditEntry.CreatedAt, faker => faker.Date.PastOffset()));
16+
.RuleFor(auditEntry => auditEntry.CreatedAt, faker => faker.Date.PastOffset()
17+
.TruncateToWholeMilliseconds()));
1718

1819
public Faker<AuditEntry> AuditEntry => _lazyAuditEntryFaker.Value;
1920
}

test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/QueryStringFakers.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ internal sealed class QueryStringFakers : FakerContainer
3131
new Faker<Comment>()
3232
.UseSeed(GetFakerSeed())
3333
.RuleFor(comment => comment.Text, faker => faker.Lorem.Paragraph())
34-
.RuleFor(comment => comment.CreatedAt, faker => faker.Date.Past()));
34+
.RuleFor(comment => comment.CreatedAt, faker => faker.Date.Past()
35+
.TruncateToWholeMilliseconds()));
3536

3637
private readonly Lazy<Faker<WebAccount>> _lazyWebAccountFaker = new(() =>
3738
new Faker<WebAccount>()
@@ -57,7 +58,8 @@ internal sealed class QueryStringFakers : FakerContainer
5758
new Faker<Appointment>()
5859
.UseSeed(GetFakerSeed())
5960
.RuleFor(appointment => appointment.Title, faker => faker.Random.Word())
60-
.RuleFor(appointment => appointment.StartTime, faker => faker.Date.FutureOffset())
61+
.RuleFor(appointment => appointment.StartTime, faker => faker.Date.FutureOffset()
62+
.TruncateToWholeMilliseconds())
6163
.RuleFor(appointment => appointment.EndTime, (faker, appointment) => appointment.StartTime.AddHours(faker.Random.Double(1, 4))));
6264

6365
public Faker<Blog> Blog => _lazyBlogFaker.Value;

test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/ReadWriteFakers.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ internal sealed class ReadWriteFakers : FakerContainer
1313
new Faker<WorkItem>()
1414
.UseSeed(GetFakerSeed())
1515
.RuleFor(workItem => workItem.Description, faker => faker.Lorem.Sentence())
16-
.RuleFor(workItem => workItem.DueAt, faker => faker.Date.Future())
16+
.RuleFor(workItem => workItem.DueAt, faker => faker.Date.Future()
17+
.TruncateToWholeMilliseconds())
1718
.RuleFor(workItem => workItem.Priority, faker => faker.PickRandom<WorkItemPriority>()));
1819

1920
private readonly Lazy<Faker<WorkTag>> _lazyWorkTagFaker = new(() =>

test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/DefaultBehaviorFakers.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ internal sealed class DefaultBehaviorFakers : FakerContainer
2323
new Faker<Shipment>()
2424
.UseSeed(GetFakerSeed())
2525
.RuleFor(shipment => shipment.TrackAndTraceCode, faker => faker.Commerce.Ean13())
26-
.RuleFor(shipment => shipment.ShippedAt, faker => faker.Date.Past()));
26+
.RuleFor(shipment => shipment.ShippedAt, faker => faker.Date.Past()
27+
.TruncateToWholeMilliseconds()));
2728

2829
public Faker<Order> Orders => _orderFaker.Value;
2930
public Faker<Customer> Customers => _customerFaker.Value;

test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceConstructorInjection/InjectionFakers.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public InjectionFakers(IServiceProvider serviceProvider)
3535
new Faker<GiftCertificate>()
3636
.UseSeed(GetFakerSeed())
3737
.CustomInstantiator(_ => new GiftCertificate(ResolveDbContext()))
38-
.RuleFor(giftCertificate => giftCertificate.IssueDate, faker => faker.Date.PastOffset()));
38+
.RuleFor(giftCertificate => giftCertificate.IssueDate, faker => faker.Date.PastOffset()
39+
.TruncateToWholeMilliseconds()));
3940
}
4041

4142
private InjectionDbContext ResolveDbContext()

test/JsonApiDotNetCoreExampleTests/IntegrationTests/Serialization/SerializationFakers.cs

+2-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ internal sealed class SerializationFakers : FakerContainer
2121
new Faker<Meeting>()
2222
.UseSeed(GetFakerSeed())
2323
.RuleFor(meeting => meeting.Title, faker => faker.Lorem.Word())
24-
.RuleFor(meeting => meeting.StartTime, faker => TruncateToWholeMilliseconds(faker.Date.FutureOffset()))
24+
.RuleFor(meeting => meeting.StartTime, faker => faker.Date.FutureOffset()
25+
.TruncateToWholeMilliseconds())
2526
.RuleFor(meeting => meeting.Duration, faker => faker.PickRandom(MeetingDurations))
2627
.RuleFor(meeting => meeting.Latitude, faker => faker.Address.Latitude())
2728
.RuleFor(meeting => meeting.Longitude, faker => faker.Address.Longitude()));
@@ -33,13 +34,5 @@ internal sealed class SerializationFakers : FakerContainer
3334

3435
public Faker<Meeting> Meeting => _lazyMeetingFaker.Value;
3536
public Faker<MeetingAttendee> MeetingAttendee => _lazyMeetingAttendeeFaker.Value;
36-
37-
private static DateTimeOffset TruncateToWholeMilliseconds(DateTimeOffset value)
38-
{
39-
long ticksToSubtract = value.DateTime.Ticks % TimeSpan.TicksPerMillisecond;
40-
long ticksInWholeMilliseconds = value.DateTime.Ticks - ticksToSubtract;
41-
42-
return new DateTimeOffset(new DateTime(ticksInWholeMilliseconds), value.Offset);
43-
}
4437
}
4538
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace TestBuildingBlocks
4+
{
5+
public static class DateTimeExtensions
6+
{
7+
// The milliseconds precision in DateTime/DateTimeOffset values that fakers produce is higher
8+
// than what PostgreSQL can store. This results in our resource change tracker to detect
9+
// that the time stored in database differs from the time in the request body.
10+
// While that's technically correct, we don't want such side effects influencing our tests everywhere.
11+
12+
public static DateTimeOffset TruncateToWholeMilliseconds(this DateTimeOffset value)
13+
{
14+
DateTime dateTime = TruncateToWholeMilliseconds(value.DateTime);
15+
16+
// PostgreSQL is unable to store the timezone in the database, so it always uses the local zone.
17+
// See https://www.npgsql.org/doc/types/datetime.html.
18+
// We're taking the local zone here, to prevent our change tracker to detect that the time
19+
// stored in database differs from the time in the request body. While that's technically
20+
// correct, we don't want such side effects influencing our tests everywhere.
21+
TimeSpan offset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
22+
23+
return new DateTimeOffset(dateTime, offset);
24+
}
25+
26+
public static DateTime TruncateToWholeMilliseconds(this DateTime value)
27+
{
28+
long ticksToSubtract = value.Ticks % TimeSpan.TicksPerMillisecond;
29+
long ticksInWholeMilliseconds = value.Ticks - ticksToSubtract;
30+
31+
return new DateTime(ticksInWholeMilliseconds, value.Kind);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)