Skip to content

Commit 03e321a

Browse files
author
Bart Koelman
committed
Adapt for breaking changes in PostgreSQL provider for EF Core 6
1 parent 3d1c590 commit 03e321a

File tree

15 files changed

+147
-57
lines changed

15 files changed

+147
-57
lines changed

src/JsonApiDotNetCore/Resources/Internal/RuntimeTypeConverter.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Globalization;
23
using JetBrains.Annotations;
34

45
#pragma warning disable AV1008 // Class should not be static
@@ -48,9 +49,15 @@ public static class RuntimeTypeConverter
4849
return isNullableTypeRequested ? (Guid?)convertedValue : convertedValue;
4950
}
5051

52+
if (nonNullableType == typeof(DateTime))
53+
{
54+
DateTime convertedValue = DateTime.Parse(stringValue, null, DateTimeStyles.RoundtripKind);
55+
return isNullableTypeRequested ? (DateTime?)convertedValue : convertedValue;
56+
}
57+
5158
if (nonNullableType == typeof(DateTimeOffset))
5259
{
53-
DateTimeOffset convertedValue = DateTimeOffset.Parse(stringValue);
60+
DateTimeOffset convertedValue = DateTimeOffset.Parse(stringValue, null, DateTimeStyles.RoundtripKind);
5461
return isNullableTypeRequested ? (DateTimeOffset?)convertedValue : convertedValue;
5562
}
5663

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public async Task Returns_resource_meta_in_create_resource_with_side_effects()
5757
attributes = new
5858
{
5959
title = newTitle1,
60-
releasedAt = 1.January(2018)
60+
releasedAt = 1.January(2018).AsUtc()
6161
}
6262
}
6363
},
@@ -70,7 +70,7 @@ public async Task Returns_resource_meta_in_create_resource_with_side_effects()
7070
attributes = new
7171
{
7272
title = newTitle2,
73-
releasedAt = 23.August(1994)
73+
releasedAt = 23.August(1994).AsUtc()
7474
}
7575
}
7676
}

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.QueryStrings
1616
{
1717
public sealed class AtomicQueryStringTests : IClassFixture<IntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext>>
1818
{
19-
private static readonly DateTime FrozenTime = 30.July(2018).At(13, 46, 12);
19+
private static readonly DateTime FrozenTime = 30.July(2018).At(13, 46, 12).AsUtc();
2020

2121
private readonly IntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext> _testContext;
2222
private readonly OperationsFakers _fakers = new();

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Text.Json.Serialization;
77
using System.Threading.Tasks;
88
using FluentAssertions;
9-
using FluentAssertions.Common;
109
using FluentAssertions.Extensions;
1110
using Humanizer;
1211
using JsonApiDotNetCore.Configuration;
@@ -134,12 +133,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
134133
}
135134

136135
[Fact]
137-
public async Task Can_filter_equality_on_type_DateTime()
136+
public async Task Can_filter_equality_on_type_DateTime_in_local_time_zone()
138137
{
139138
// Arrange
140139
var resource = new FilterableResource
141140
{
142-
SomeDateTime = 27.January(2003).At(11, 22, 33, 44)
141+
SomeDateTimeInLocalZone = 27.January(2003).At(11, 22, 33, 44)
143142
};
144143

145144
await _testContext.RunOnDatabaseAsync(async dbContext =>
@@ -149,7 +148,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
149148
await dbContext.SaveChangesAsync();
150149
});
151150

152-
string route = $"/filterableResources?filter=equals(someDateTime,'{resource.SomeDateTime:O}')";
151+
string route = $"/filterableResources?filter=equals(someDateTimeInLocalZone,'{resource.SomeDateTimeInLocalZone:O}')";
153152

154153
// Act
155154
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
@@ -159,17 +158,47 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
159158

160159
responseDocument.Data.ManyValue.ShouldHaveCount(1);
161160

162-
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTime")
163-
.With(value => value.As<DateTime>().Should().BeCloseTo(resource.SomeDateTime));
161+
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInLocalZone")
162+
.With(value => value.As<DateTime>().Should().BeCloseTo(resource.SomeDateTimeInLocalZone));
164163
}
165164

166165
[Fact]
167-
public async Task Can_filter_equality_on_type_DateTimeOffset()
166+
public async Task Can_filter_equality_on_type_DateTime_in_UTC_time_zone()
168167
{
169168
// Arrange
170169
var resource = new FilterableResource
171170
{
172-
SomeDateTimeOffset = 27.January(2003).At(11, 22, 33, 44).ToDateTimeOffset(TimeSpan.FromHours(3))
171+
SomeDateTimeInUtcZone = 27.January(2003).At(11, 22, 33, 44).AsUtc()
172+
};
173+
174+
await _testContext.RunOnDatabaseAsync(async dbContext =>
175+
{
176+
await dbContext.ClearTableAsync<FilterableResource>();
177+
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
178+
await dbContext.SaveChangesAsync();
179+
});
180+
181+
string route = $"/filterableResources?filter=equals(someDateTimeInUtcZone,'{resource.SomeDateTimeInUtcZone:O}')";
182+
183+
// Act
184+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
185+
186+
// Assert
187+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
188+
189+
responseDocument.Data.ManyValue.ShouldHaveCount(1);
190+
191+
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInUtcZone")
192+
.With(value => value.As<DateTime>().Should().BeCloseTo(resource.SomeDateTimeInUtcZone));
193+
}
194+
195+
[Fact]
196+
public async Task Can_filter_equality_on_type_DateTimeOffset_in_UTC_time_zone()
197+
{
198+
// Arrange
199+
var resource = new FilterableResource
200+
{
201+
SomeDateTimeOffset = 27.January(2003).At(11, 22, 33, 44).AsUtc()
173202
};
174203

175204
await _testContext.RunOnDatabaseAsync(async dbContext =>
@@ -282,8 +311,8 @@ public async Task Can_filter_is_null_on_type(string propertyName)
282311
SomeNullableDecimal = 1,
283312
SomeNullableDouble = 1,
284313
SomeNullableGuid = Guid.NewGuid(),
285-
SomeNullableDateTime = 1.January(2001),
286-
SomeNullableDateTimeOffset = 1.January(2001).ToDateTimeOffset(TimeSpan.FromHours(-1)),
314+
SomeNullableDateTime = 1.January(2001).AsUtc(),
315+
SomeNullableDateTimeOffset = 1.January(2001).AsUtc(),
287316
SomeNullableTimeSpan = TimeSpan.FromHours(1),
288317
SomeNullableEnum = DayOfWeek.Friday
289318
};
@@ -332,8 +361,8 @@ public async Task Can_filter_is_not_null_on_type(string propertyName)
332361
SomeNullableDecimal = 1,
333362
SomeNullableDouble = 1,
334363
SomeNullableGuid = Guid.NewGuid(),
335-
SomeNullableDateTime = 1.January(2001),
336-
SomeNullableDateTimeOffset = 1.January(2001).ToDateTimeOffset(TimeSpan.FromHours(-1)),
364+
SomeNullableDateTime = 1.January(2001).AsUtc(),
365+
SomeNullableDateTimeOffset = 1.January(2001).AsUtc(),
337366
SomeNullableTimeSpan = TimeSpan.FromHours(1),
338367
SomeNullableEnum = DayOfWeek.Friday
339368
};

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using JetBrains.Annotations;
22
using Microsoft.EntityFrameworkCore;
33

4+
// @formatter:wrap_chained_method_calls chop_always
5+
46
namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.Filtering
57
{
68
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
@@ -12,5 +14,12 @@ public FilterDbContext(DbContextOptions<FilterDbContext> options)
1214
: base(options)
1315
{
1416
}
17+
18+
protected override void OnModelCreating(ModelBuilder builder)
19+
{
20+
builder.Entity<FilterableResource>()
21+
.Property(resource => resource.SomeDateTimeInLocalZone)
22+
.HasColumnType("timestamp without time zone");
23+
}
1524
}
1625
}

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDepthTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,8 @@ public async Task Can_filter_in_multiple_scopes()
507507
blogs[1].Owner!.Posts[0].Caption = "One";
508508
blogs[1].Owner!.Posts[1].Caption = "Two";
509509
blogs[1].Owner!.Posts[1].Comments = _fakers.Comment.Generate(2).ToHashSet();
510-
blogs[1].Owner!.Posts[1].Comments.ElementAt(0).CreatedAt = 1.January(2000);
511-
blogs[1].Owner!.Posts[1].Comments.ElementAt(1).CreatedAt = 10.January(2010);
510+
blogs[1].Owner!.Posts[1].Comments.ElementAt(0).CreatedAt = 1.January(2000).AsUtc();
511+
blogs[1].Owner!.Posts[1].Comments.ElementAt(1).CreatedAt = 10.January(2010).AsUtc();
512512

513513
await _testContext.RunOnDatabaseAsync(async dbContext =>
514514
{
@@ -522,7 +522,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
522522
const string route = "/blogs?include=owner.posts.comments&" +
523523
"filter=and(equals(title,'Technology'),has(owner.posts),equals(owner.userName,'Smith'))&" +
524524
"filter[owner.posts]=equals(caption,'Two')&" +
525-
"filter[owner.posts.comments]=greaterThan(createdAt,'2005-05-05')";
525+
"filter[owner.posts.comments]=greaterThan(createdAt,'2005-05-05Z')";
526526

527527
// @formatter:keep_existing_linebreaks restore
528528

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
34
using System.Net;
45
using System.Net.Http;
56
using System.Threading.Tasks;
67
using System.Web;
78
using FluentAssertions;
9+
using FluentAssertions.Extensions;
810
using Humanizer;
911
using JsonApiDotNetCore.Configuration;
1012
using JsonApiDotNetCore.Queries.Expressions;
@@ -350,18 +352,18 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
350352
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterThan, "2001-01-01")]
351353
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterOrEqual, "2001-01-05")]
352354
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterOrEqual, "2001-01-09")]
353-
public async Task Can_filter_comparison_on_DateTime(string matchingDateTime, string nonMatchingDateTime, ComparisonOperator filterOperator,
354-
string filterDateTime)
355+
public async Task Can_filter_comparison_on_DateTime_in_local_time_zone(string matchingDateTime, string nonMatchingDateTime,
356+
ComparisonOperator filterOperator, string filterDateTime)
355357
{
356358
// Arrange
357359
var resource = new FilterableResource
358360
{
359-
SomeDateTime = DateTime.ParseExact(matchingDateTime, "yyyy-MM-dd", null)
361+
SomeDateTimeInLocalZone = DateTime.Parse(matchingDateTime, CultureInfo.InvariantCulture).AsLocal()
360362
};
361363

362364
var otherResource = new FilterableResource
363365
{
364-
SomeDateTime = DateTime.ParseExact(nonMatchingDateTime, "yyyy-MM-dd", null)
366+
SomeDateTimeInLocalZone = DateTime.Parse(nonMatchingDateTime, CultureInfo.InvariantCulture).AsLocal()
365367
};
366368

367369
await _testContext.RunOnDatabaseAsync(async dbContext =>
@@ -371,8 +373,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
371373
await dbContext.SaveChangesAsync();
372374
});
373375

374-
string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTime," +
375-
$"'{DateTime.ParseExact(filterDateTime, "yyyy-MM-dd", null)}')";
376+
string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTimeInLocalZone,'{filterDateTime}')";
376377

377378
// Act
378379
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
@@ -382,8 +383,52 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
382383

383384
responseDocument.Data.ManyValue.ShouldHaveCount(1);
384385

385-
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTime")
386-
.With(value => value.As<DateTime>().Should().BeCloseTo(resource.SomeDateTime));
386+
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInLocalZone")
387+
.With(value => value.As<DateTime>().Should().BeCloseTo(resource.SomeDateTimeInLocalZone));
388+
}
389+
390+
[Theory]
391+
[InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessThan, "2001-01-05Z")]
392+
[InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessThan, "2001-01-09Z")]
393+
[InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessOrEqual, "2001-01-05Z")]
394+
[InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessOrEqual, "2001-01-01Z")]
395+
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterThan, "2001-01-05Z")]
396+
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterThan, "2001-01-01Z")]
397+
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterOrEqual, "2001-01-05Z")]
398+
[InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterOrEqual, "2001-01-09Z")]
399+
public async Task Can_filter_comparison_on_DateTime_in_UTC_time_zone(string matchingDateTime, string nonMatchingDateTime,
400+
ComparisonOperator filterOperator, string filterDateTime)
401+
{
402+
// Arrange
403+
var resource = new FilterableResource
404+
{
405+
SomeDateTimeInUtcZone = DateTime.Parse(matchingDateTime, CultureInfo.InvariantCulture).AsUtc()
406+
};
407+
408+
var otherResource = new FilterableResource
409+
{
410+
SomeDateTimeInUtcZone = DateTime.Parse(nonMatchingDateTime, CultureInfo.InvariantCulture).AsUtc()
411+
};
412+
413+
await _testContext.RunOnDatabaseAsync(async dbContext =>
414+
{
415+
await dbContext.ClearTableAsync<FilterableResource>();
416+
dbContext.FilterableResources.AddRange(resource, otherResource);
417+
await dbContext.SaveChangesAsync();
418+
});
419+
420+
string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTimeInUtcZone,'{filterDateTime}')";
421+
422+
// Act
423+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
424+
425+
// Assert
426+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
427+
428+
responseDocument.Data.ManyValue.ShouldHaveCount(1);
429+
430+
responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInUtcZone")
431+
.With(value => value.As<DateTime>().Should().BeCloseTo(resource.SomeDateTimeInUtcZone));
387432
}
388433

389434
[Theory]

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ public sealed class FilterableResource : Identifiable<int>
5959
public Guid? SomeNullableGuid { get; set; }
6060

6161
[Attr]
62-
public DateTime SomeDateTime { get; set; }
62+
public DateTime SomeDateTimeInLocalZone { get; set; }
63+
64+
[Attr]
65+
public DateTime SomeDateTimeInUtcZone { get; set; }
6366

6467
[Attr]
6568
public DateTime? SomeNullableDateTime { get; set; }

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SerializerIgnoreConditionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public async Task Applies_configuration_for_ignore_condition(JsonIgnoreCondition
4040
calendar.Appointments = _fakers.Appointment.Generate(1).ToHashSet();
4141
calendar.Appointments.Single().Description = null;
4242
calendar.Appointments.Single().StartTime = default;
43-
calendar.Appointments.Single().EndTime = 1.January(2001);
43+
calendar.Appointments.Single().EndTime = 1.January(2001).AsUtc();
4444

4545
await RunOnDatabaseAsync(async dbContext =>
4646
{

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Sorting/SortTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,9 @@ public async Task Can_sort_on_multiple_fields_in_multiple_scopes()
320320
blogs[0].Posts[3].Url = "";
321321

322322
blogs[0].Posts[0].Comments = _fakers.Comment.Generate(3).ToHashSet();
323-
blogs[0].Posts[0].Comments.ElementAt(0).CreatedAt = 1.January(2015);
324-
blogs[0].Posts[0].Comments.ElementAt(1).CreatedAt = 1.January(2014);
325-
blogs[0].Posts[0].Comments.ElementAt(2).CreatedAt = 1.January(2016);
323+
blogs[0].Posts[0].Comments.ElementAt(0).CreatedAt = 1.January(2015).AsUtc();
324+
blogs[0].Posts[0].Comments.ElementAt(1).CreatedAt = 1.January(2014).AsUtc();
325+
blogs[0].Posts[0].Comments.ElementAt(2).CreatedAt = 1.January(2016).AsUtc();
326326

327327
await _testContext.RunOnDatabaseAsync(async dbContext =>
328328
{
@@ -412,8 +412,8 @@ public async Task Can_sort_in_multiple_scopes()
412412
blogs[1].Owner!.Posts[1].Caption = "Two";
413413

414414
blogs[1].Owner!.Posts[1].Comments = _fakers.Comment.Generate(2).ToHashSet();
415-
blogs[1].Owner!.Posts[1].Comments.ElementAt(0).CreatedAt = 1.January(2000);
416-
blogs[1].Owner!.Posts[1].Comments.ElementAt(0).CreatedAt = 10.January(2010);
415+
blogs[1].Owner!.Posts[1].Comments.ElementAt(0).CreatedAt = 1.January(2000).AsUtc();
416+
blogs[1].Owner!.Posts[1].Comments.ElementAt(0).CreatedAt = 10.January(2010).AsUtc();
417417

418418
await _testContext.RunOnDatabaseAsync(async dbContext =>
419419
{

0 commit comments

Comments
 (0)