options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
IdentityServerServerSideSessionStore sut = CreateSut(context);
SessionModel result = await sut.GetSession("missing-key");
@@ -61,7 +68,7 @@ public async Task GetSession_WhenDoesntExist_ShouldReturnNull(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
string key = "session-key-1";
IdentityServerServerSideSessions seeded = new IdentityServerServerSideSessions
@@ -71,9 +78,9 @@ public async Task GetSession_WhenExist_ShouldReturnValue(DbContextOptions act = async () => await sut.CreateSession(newSession);
@@ -115,7 +122,7 @@ public async Task CreateSession_WhenKeyNullOrEmpty_ShouldThrowArgumentException(
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task CreateSession_WhenSessionAlreadyExistsWithKey_ShouldLogError(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
string key = "duplicate-key";
context.ServerSideSessions.Add(new IdentityServerServerSideSessions
@@ -125,15 +132,15 @@ public async Task CreateSession_WhenSessionAlreadyExistsWithKey_ShouldLogError(D
SubjectId = "sub-existing",
SessionId = "sid-existing",
DisplayName = "existing",
- Created = DateTime.UtcNow.AddMinutes(-20),
- Renewed = DateTime.UtcNow.AddMinutes(-10),
- Expires = DateTime.UtcNow.AddMinutes(20),
+ Created = FakeNow.AddMinutes(-20),
+ Renewed = FakeNow.AddMinutes(-10),
+ Expires = FakeNow.AddMinutes(20),
Data = "{\"state\":\"existing\"}"
});
await context.SaveChangesAsync();
IdentityServerServerSideSessionStore sut = CreateSut(context);
- var newSession = BuildSessionModel(key, "sub-new", "sid-new", "new");
+ SessionModel newSession = BuildSessionModel(key, "sub-new", "sid-new", "new");
await sut.CreateSession(newSession);
@@ -143,16 +150,16 @@ public async Task CreateSession_WhenSessionAlreadyExistsWithKey_ShouldLogError(D
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task CreateSession_WhenSessionDoesntExistsWithKey_ShouldStoreSessionInDatabase(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
string key = "new-key";
- var session = BuildSessionModel(key, "sub-123", "sid-123", "display-123");
+ SessionModel session = BuildSessionModel(key, "sub-123", "sid-123", "display-123");
IdentityServerServerSideSessionStore sut = CreateSut(context);
await sut.CreateSession(session);
- var stored = await context.ServerSideSessions
+ IdentityServerServerSideSessions stored = await context.ServerSideSessions
.AsNoTracking()
.SingleOrDefaultAsync(x => x.Key == key, cancellationToken: TestContext.Current.CancellationToken);
@@ -174,10 +181,10 @@ public async Task CreateSession_WhenSessionDoesntExistsWithKey_ShouldStoreSessio
[InlineData(" ")]
public async Task UpdateSession_WhenKeyNullOrEmpty_ShouldThrowArgumentException(string key)
{
- await using var context = await CreateCleanContext(TestDatabaseProviders.FirstOrDefault());
+ await using PersistedGrantDbContext context = await CreateCleanContext(TestDatabaseProviders.FirstOrDefault());
IdentityServerServerSideSessionStore sut = CreateSut(context);
- var session = BuildSessionModel(key, "sub-new", "sid-new", "new");
+ SessionModel session = BuildSessionModel(key, "sub-new", "sid-new", "new");
Func act = async () => await sut.UpdateSession(session);
@@ -187,10 +194,10 @@ public async Task UpdateSession_WhenKeyNullOrEmpty_ShouldThrowArgumentException(
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task UpdateSession_WhenSessionDoesntExistsWithKey_ShouldLogError(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
IdentityServerServerSideSessionStore sut = CreateSut(context);
- var session = BuildSessionModel("missing-update-key", "sub", "sid", "display");
+ SessionModel session = BuildSessionModel("missing-update-key", "sub", "sid", "display");
await sut.UpdateSession(session);
@@ -200,7 +207,7 @@ public async Task UpdateSession_WhenSessionDoesntExistsWithKey_ShouldLogError(Db
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task UpdateSession_WhenSessionExistsWithKey_ShouldUpdateStoredSession(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
string key = "update-key";
context.ServerSideSessions.Add(new IdentityServerServerSideSessions
@@ -210,25 +217,25 @@ public async Task UpdateSession_WhenSessionExistsWithKey_ShouldUpdateStoredSessi
SubjectId = "old-sub",
SessionId = "old-sid",
DisplayName = "old-display",
- Created = DateTime.UtcNow.AddHours(-2),
- Renewed = DateTime.UtcNow.AddHours(-1),
- Expires = DateTime.UtcNow.AddMinutes(5),
+ Created = FakeNow.AddHours(-2),
+ Renewed = FakeNow.AddHours(-1),
+ Expires = FakeNow.AddMinutes(5),
Data = "{\"version\":1}"
});
await context.SaveChangesAsync();
- var updated = BuildSessionModel(key, "new-sub", "new-sid", "new-display");
+ SessionModel updated = BuildSessionModel(key, "new-sub", "new-sid", "new-display");
updated.Scheme = "new-scheme";
updated.Data = "{\"version\":2}";
- updated.Created = DateTime.UtcNow.AddHours(-3);
- updated.Renewed = DateTime.UtcNow.AddMinutes(-1);
- updated.Expires = DateTime.UtcNow.AddHours(2);
+ updated.Created = FakeNow.AddHours(-3);
+ updated.Renewed = FakeNow.AddMinutes(-1);
+ updated.Expires = FakeNow.AddHours(2);
IdentityServerServerSideSessionStore sut = CreateSut(context);
await sut.UpdateSession(updated);
- var stored = await context.ServerSideSessions
+ IdentityServerServerSideSessions stored = await context.ServerSideSessions
.AsNoTracking()
.SingleAsync(x => x.Key == key, cancellationToken: TestContext.Current.CancellationToken);
@@ -249,7 +256,7 @@ public async Task UpdateSession_WhenSessionExistsWithKey_ShouldUpdateStoredSessi
[InlineData(" ")]
public async Task DeleteSession_WhenKeyNullOrEmpty_ShouldThrowArgumentException(string key)
{
- await using var context = await CreateCleanContext(TestDatabaseProviders.FirstOrDefault());
+ await using PersistedGrantDbContext context = await CreateCleanContext(TestDatabaseProviders.FirstOrDefault());
IdentityServerServerSideSessionStore sut = CreateSut(context);
Func act = async () => await sut.DeleteSession(key);
@@ -260,7 +267,7 @@ public async Task DeleteSession_WhenKeyNullOrEmpty_ShouldThrowArgumentException(
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task DeleteSession_WhenSessionDoesntExistsWithKey_ShouldLogError(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
IdentityServerServerSideSessionStore sut = CreateSut(context);
await sut.DeleteSession("missing-delete-key");
@@ -271,7 +278,7 @@ public async Task DeleteSession_WhenSessionDoesntExistsWithKey_ShouldLogError(Db
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task DeleteSession_WhenSessionExistsWithKey_ShouldDeleteStoredSession(DbContextOptions options)
{
- await using var context = await CreateCleanContext(options);
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
string key = "delete-key";
context.ServerSideSessions.Add(new IdentityServerServerSideSessions
@@ -281,9 +288,9 @@ public async Task DeleteSession_WhenSessionExistsWithKey_ShouldDeleteStoredSessi
SubjectId = "sub-delete",
SessionId = "sid-delete",
DisplayName = "delete me",
- Created = DateTime.UtcNow.AddMinutes(-30),
- Renewed = DateTime.UtcNow.AddMinutes(-15),
- Expires = DateTime.UtcNow.AddMinutes(30),
+ Created = FakeNow.AddMinutes(-30),
+ Renewed = FakeNow.AddMinutes(-15),
+ Expires = FakeNow.AddMinutes(30),
Data = "{\"delete\":true}"
});
await context.SaveChangesAsync();
@@ -292,7 +299,7 @@ public async Task DeleteSession_WhenSessionExistsWithKey_ShouldDeleteStoredSessi
await sut.DeleteSession(key);
- var stored = await context.ServerSideSessions
+ IdentityServerServerSideSessions stored = await context.ServerSideSessions
.AsNoTracking()
.SingleOrDefaultAsync(x => x.Key == key, cancellationToken: TestContext.Current.CancellationToken);
@@ -344,6 +351,100 @@ await context.ServerSideSessions.AddRangeAsync([
actual.Should().Contain(x => x.Key == "key-1");
actual.Should().Contain(x => x.Key == "key-6");
}
+
+ [Theory, MemberData(nameof(TestDatabaseProviders))]
+ public async Task GetAndRemoveExpiredSessions_WhenNoExpiredSessionsExist_ShouldRemoveNothingAndReturnEmptyCollection(DbContextOptions options)
+ {
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
+
+ IdentityServerServerSideSessions validSession0 = FakeSessionSession("123", "session1");
+ IdentityServerServerSideSessions validSession1 = FakeSessionSession("456", "session2");
+ context.ServerSideSessions.Add(validSession0);
+ context.ServerSideSessions.Add(validSession1);
+ await context.SaveChangesAsync();
+
+ IdentityServerServerSideSessionStore sut = CreateSut(context);
+
+ List actual = (await sut.GetAndRemoveExpiredSessions()).ToList();
+
+ actual.Should().BeEmpty();
+ }
+
+ [Theory, MemberData(nameof(TestDatabaseProviders))]
+ public async Task GetAndRemoveExpiredSessions_WhenExpiredSessionsExist_AndUnderBatchSize_ShouldDeleteExpiredSessionsAndReturnACollectionContainingRemovedSessions(DbContextOptions options)
+ {
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
+
+ IdentityServerServerSideSessions expiredSession0 = FakeSessionSession("123", "session1", true);
+ IdentityServerServerSideSessions expiredSession1 = FakeSessionSession("456", "session2", true);
+ context.ServerSideSessions.Add(expiredSession0);
+ context.ServerSideSessions.Add(expiredSession1);
+ await context.SaveChangesAsync();
+
+ IdentityServerServerSideSessionStore sut = CreateSut(context);
+
+ List actual = (await sut.GetAndRemoveExpiredSessions()).ToList();
+
+ actual.Should().HaveCount(2);
+ actual.Should().Contain(x => x.Key == expiredSession0.Key);
+ actual.Should().Contain(x => x.Key == expiredSession1.Key);
+ }
+
+ [Theory, MemberData(nameof(TestDatabaseProviders))]
+ public async Task GetAndRemoveExpiredSessions_WhenExpiredSessionsExist_AndExceedBatchSize_ShouldDeleteAndReturnExpiredSessions_WithACountOfBatchSize(DbContextOptions options)
+ {
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
+
+ IdentityServerServerSideSessions expiredSession0 = FakeSessionSession("123", "session1", true);
+ IdentityServerServerSideSessions expiredSession1 = FakeSessionSession("456", "session2", true);
+ IdentityServerServerSideSessions expiredSession2 = FakeSessionSession("789", "session3", true);
+ IdentityServerServerSideSessions validSession0 = FakeSessionSession("234", "session4");
+ context.ServerSideSessions.Add(expiredSession0);
+ context.ServerSideSessions.Add(expiredSession1);
+ context.ServerSideSessions.Add(expiredSession2);
+ context.ServerSideSessions.Add(validSession0);
+ await context.SaveChangesAsync();
+
+ IdentityServerServerSideSessionStore sut = CreateSut(context);
+
+ List actual = (await sut.GetAndRemoveExpiredSessions(2)).ToList();
+
+ actual.Should().HaveCount(2);
+ actual.Should().Contain(x => x.Key == expiredSession0.Key);
+ actual.Should().Contain(x => x.Key == expiredSession1.Key);
+ }
+
+ //TODO: Finish implementing test
+ [Theory, MemberData(nameof(TestDatabaseProviders))]
+ public async Task GetAndRemoveExpiredSessions_WhenUnspecifiedTimezoneInDbEntities_ShouldBeTreatedAsUtc(DbContextOptions options)
+ {
+ //Ensuring timezone info is the same across environments
+ using var mockedTimezone = new LocalTimeZoneInfoMocker(TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
+
+ DateTime nowUnspecified = new(2025, 02, 27, 12, 12, 11, DateTimeKind.Unspecified);
+
+ var testExpired = nowUnspecified.AddDays(-1);
+ var testWithin8HoursToExpiry = nowUnspecified.AddHours(2);
+
+ await using PersistedGrantDbContext context = await CreateCleanContext(options);
+
+ IdentityServerServerSideSessions expiredSession0 = FakeSessionSession("123", "session1");
+ IdentityServerServerSideSessions validSession0 = FakeSessionSession("234", "session4");
+
+ expiredSession0.Expires = testExpired;
+ validSession0.Expires = testWithin8HoursToExpiry;
+
+ context.ServerSideSessions.Add(expiredSession0);
+ context.ServerSideSessions.Add(validSession0);
+ await context.SaveChangesAsync();
+
+ IdentityServerServerSideSessionStore sut = CreateSut(context);
+
+ List actual = (await sut.GetAndRemoveExpiredSessions(2)).ToList();
+
+ actual.Should().HaveCount(1);
+ actual.Should().Contain(x => x.Key == expiredSession0.Key);
+ }
[Theory, MemberData(nameof(TestDatabaseProviders))]
public async Task PublicMethods_WhenCalled_ShouldTelemetryTrace(DbContextOptions options)
@@ -355,11 +456,12 @@ public async Task PublicMethods_WhenCalled_ShouldTelemetryTrace(DbContextOptions
(store => store.UpdateSession(new SessionModel { Key = "FAKE_SESSION_KEY" }), "UpdateSession"),
(store => store.DeleteSession("FAKE_SESSION_KEY"), "DeleteSession"),
(store => store.FilterSessions("FAKE_SUBJECT_KEY", "FAKE_SESSION_KEY"), "FilterSessions"),
+ (store => store.GetAndRemoveExpiredSessions(), "GetAndRemoveExpiredSessions"),
];
- foreach (var method in methods)
+ foreach ((Func actMethod, string traceMethodName) method in methods)
{
- var trace = Mock.Of();
+ ITrace trace = Mock.Of();
Mock.Get(telemetry).Setup(t => t.Trace(It.IsAny(), It.IsAny