diff --git a/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs b/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs
index a4935f76b..46ac8c83d 100644
--- a/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs
+++ b/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs
@@ -346,6 +346,133 @@ public class TaskRunnerTests
Assert.That(userWithoutLimitCount, Is.EqualTo(20), "User without specific limit should use global limit");
}
+ ///
+ /// Test that inactive users are properly identified based on MarkUserInactiveAfterDays setting.
+ ///
+ /// Task.
+ [Test]
+ public async Task InactiveUserDetection_MarkUsersInactiveAfterDays()
+ {
+ await using var dbContext = await _testHostBuilder.GetDbContextAsync();
+
+ // Set inactive user threshold to 30 days
+ var inactiveSetting = new ServerSetting
+ {
+ Key = "MarkUserInactiveAfterDays",
+ Value = "30",
+ };
+ dbContext.ServerSettings.Add(inactiveSetting);
+ await dbContext.SaveChangesAsync();
+
+ // Create test users with different activity patterns
+ await SetupInactiveUserTest();
+
+ await _testHost.StartAsync();
+ await WaitForMaintenanceJobCompletion();
+
+ // Verify users are correctly identified as active/inactive
+ var activeUser = await dbContext.AliasVaultUsers.FirstAsync(u => u.UserName == "activeuser");
+ var inactiveUser = await dbContext.AliasVaultUsers.FirstAsync(u => u.UserName == "inactiveuser");
+ var oldUser = await dbContext.AliasVaultUsers.FirstAsync(u => u.UserName == "olduser");
+
+ // Note: The task runner doesn't directly modify user records, but the admin UI uses these settings
+ // to determine inactive status. This test verifies the setting is properly stored and retrievable.
+ var storedSetting = await dbContext.ServerSettings
+ .FirstAsync(s => s.Key == "MarkUserInactiveAfterDays");
+ Assert.That(storedSetting.Value, Is.EqualTo("30"), "MarkUserInactiveAfterDays setting should be stored correctly");
+ }
+
+ ///
+ /// Test that MaxEmailsPerInactiveUser setting enforces email limits for inactive users.
+ ///
+ /// Task.
+ [Test]
+ public async Task InactiveUserEmailLimits_EnforcesMaxEmailsPerInactiveUser()
+ {
+ await using var dbContext = await _testHostBuilder.GetDbContextAsync();
+
+ // Set inactive user threshold to 30 days
+ var inactiveSetting = new ServerSetting
+ {
+ Key = "MarkUserInactiveAfterDays",
+ Value = "30",
+ };
+ dbContext.ServerSettings.Add(inactiveSetting);
+
+ // Set max emails per inactive user to 5
+ var inactiveEmailLimitSetting = new ServerSetting
+ {
+ Key = "MaxEmailsPerInactiveUser",
+ Value = "5",
+ };
+ dbContext.ServerSettings.Add(inactiveEmailLimitSetting);
+
+ await dbContext.SaveChangesAsync();
+
+ // Create test users with different activity levels and email counts
+ await SetupInactiveUserEmailLimitsTest();
+
+ await _testHost.StartAsync();
+ await WaitForMaintenanceJobCompletion();
+
+ // Check that active user retains all emails
+ var activeUserEmailCount = await dbContext.Emails
+ .Where(e => e.To == "activeuser@test.com")
+ .CountAsync();
+ Assert.That(activeUserEmailCount, Is.EqualTo(20), "Active user should retain all emails");
+
+ // Check that inactive user has emails limited to MaxEmailsPerInactiveUser
+ var inactiveUserEmailCount = await dbContext.Emails
+ .Where(e => e.To == "inactiveuser@test.com")
+ .CountAsync();
+ Assert.That(inactiveUserEmailCount, Is.EqualTo(5), "Inactive user should have emails limited to MaxEmailsPerInactiveUser setting");
+ }
+
+ ///
+ /// Test that when MarkUserInactiveAfterDays is 0 (disabled), no users are considered inactive.
+ ///
+ /// Task.
+ [Test]
+ public async Task InactiveUserDetection_DisabledWhenZeroDays()
+ {
+ await using var dbContext = await _testHostBuilder.GetDbContextAsync();
+
+ // Set inactive user threshold to 0 (disabled)
+ var inactiveSetting = new ServerSetting
+ {
+ Key = "MarkUserInactiveAfterDays",
+ Value = "0",
+ };
+ dbContext.ServerSettings.Add(inactiveSetting);
+
+ // Set max emails per inactive user (should not be applied when inactive detection is disabled)
+ var inactiveEmailLimitSetting = new ServerSetting
+ {
+ Key = "MaxEmailsPerInactiveUser",
+ Value = "3",
+ };
+ dbContext.ServerSettings.Add(inactiveEmailLimitSetting);
+
+ await dbContext.SaveChangesAsync();
+
+ // Create test users including very old users
+ await SetupInactiveUserTest();
+
+ await _testHost.StartAsync();
+ await WaitForMaintenanceJobCompletion();
+
+ // Check that even very old users retain all emails since inactive detection is disabled
+ var oldUserEmailCount = await dbContext.Emails
+ .Where(e => e.To == "olduser@test.com")
+ .CountAsync();
+ Assert.That(oldUserEmailCount, Is.EqualTo(15), "Old user should retain all emails when inactive detection is disabled");
+
+ // Verify the setting is stored as 0
+ var storedSetting = await dbContext.ServerSettings
+ .FirstAsync(s => s.Key == "MarkUserInactiveAfterDays");
+ Assert.That(storedSetting.Value, Is.EqualTo("0"), "MarkUserInactiveAfterDays should be 0 (disabled)");
+ }
+
///
/// Creates a base email with static required fields.
///
@@ -723,4 +850,136 @@ public class TaskRunnerTests
await dbContext.SaveChangesAsync();
}
+
+ ///
+ /// Sets up test data for inactive user detection testing.
+ ///
+ /// Task.
+ private async Task SetupInactiveUserTest()
+ {
+ await using var dbContext = await _testHostBuilder.GetDbContextAsync();
+
+ // Create active user (recent activity)
+ var activeUser = new AliasVaultUser
+ {
+ UserName = "activeuser",
+ Email = "activeuser@test.com",
+ LastActivityDate = DateTime.UtcNow.AddDays(-5), // Active within 30 days
+ };
+ dbContext.AliasVaultUsers.Add(activeUser);
+
+ // Create inactive user (no recent activity)
+ var inactiveUser = new AliasVaultUser
+ {
+ UserName = "inactiveuser",
+ Email = "inactiveuser@test.com",
+ LastActivityDate = DateTime.UtcNow.AddDays(-45), // Inactive for 45 days
+ };
+ dbContext.AliasVaultUsers.Add(inactiveUser);
+
+ // Create old user (very old, no activity)
+ var oldUser = new AliasVaultUser
+ {
+ UserName = "olduser",
+ Email = "olduser@test.com",
+ LastActivityDate = null, // Never logged in
+ CreatedAt = DateTime.UtcNow.AddDays(-100), // Created 100 days ago
+ };
+ dbContext.AliasVaultUsers.Add(oldUser);
+
+ await dbContext.SaveChangesAsync();
+
+ // Create encryption key
+ var encryptionKey = new UserEncryptionKey
+ {
+ Id = Guid.NewGuid(),
+ UserId = activeUser.Id,
+ PublicKey = "test-encryption-key",
+ CreatedAt = DateTime.UtcNow,
+ UpdatedAt = DateTime.UtcNow,
+ };
+ dbContext.UserEncryptionKeys.Add(encryptionKey);
+
+ // Create email claims for each user
+ dbContext.UserEmailClaims.Add(new UserEmailClaim { UserId = activeUser.Id, Address = "activeuser@test.com", AddressLocal = "activeuser", AddressDomain = "test.com" });
+ dbContext.UserEmailClaims.Add(new UserEmailClaim { UserId = inactiveUser.Id, Address = "inactiveuser@test.com", AddressLocal = "inactiveuser", AddressDomain = "test.com" });
+ dbContext.UserEmailClaims.Add(new UserEmailClaim { UserId = oldUser.Id, Address = "olduser@test.com", AddressLocal = "olduser", AddressDomain = "test.com" });
+
+ // Create emails for each user
+ for (int i = 0; i < 10; i++)
+ {
+ var dateCreated = DateTime.UtcNow.AddDays(-i);
+ dbContext.Emails.Add(CreateTestEmail("activeuser@test.com", encryptionKey, $"Active User Email {i}", dateCreated));
+ dbContext.Emails.Add(CreateTestEmail("inactiveuser@test.com", encryptionKey, $"Inactive User Email {i}", dateCreated));
+ }
+
+ // Create 15 emails for old user
+ for (int i = 0; i < 15; i++)
+ {
+ var dateCreated = DateTime.UtcNow.AddDays(-i);
+ dbContext.Emails.Add(CreateTestEmail("olduser@test.com", encryptionKey, $"Old User Email {i}", dateCreated));
+ }
+
+ await dbContext.SaveChangesAsync();
+ }
+
+ ///
+ /// Sets up test data for inactive user email limits testing.
+ ///
+ /// Task.
+ private async Task SetupInactiveUserEmailLimitsTest()
+ {
+ await using var dbContext = await _testHostBuilder.GetDbContextAsync();
+
+ // Create active user (recent activity)
+ var activeUser = new AliasVaultUser
+ {
+ UserName = "activeuser",
+ Email = "activeuser@test.com",
+ LastActivityDate = DateTime.UtcNow.AddDays(-5), // Active within 30 days
+ };
+ dbContext.AliasVaultUsers.Add(activeUser);
+
+ // Create inactive user (no recent activity)
+ var inactiveUser = new AliasVaultUser
+ {
+ UserName = "inactiveuser",
+ Email = "inactiveuser@test.com",
+ LastActivityDate = DateTime.UtcNow.AddDays(-45), // Inactive for 45 days
+ };
+ dbContext.AliasVaultUsers.Add(inactiveUser);
+
+ await dbContext.SaveChangesAsync();
+
+ // Create encryption key
+ var encryptionKey = new UserEncryptionKey
+ {
+ Id = Guid.NewGuid(),
+ UserId = activeUser.Id,
+ PublicKey = "test-encryption-key",
+ CreatedAt = DateTime.UtcNow,
+ UpdatedAt = DateTime.UtcNow,
+ };
+ dbContext.UserEncryptionKeys.Add(encryptionKey);
+
+ // Create email claims for each user
+ dbContext.UserEmailClaims.Add(new UserEmailClaim { UserId = activeUser.Id, Address = "activeuser@test.com", AddressLocal = "activeuser", AddressDomain = "test.com" });
+ dbContext.UserEmailClaims.Add(new UserEmailClaim { UserId = inactiveUser.Id, Address = "inactiveuser@test.com", AddressLocal = "inactiveuser", AddressDomain = "test.com" });
+
+ // Create 20 emails for active user
+ for (int i = 0; i < 20; i++)
+ {
+ var dateCreated = DateTime.UtcNow.AddDays(-i);
+ dbContext.Emails.Add(CreateTestEmail("activeuser@test.com", encryptionKey, $"Active User Email {i}", dateCreated));
+ }
+
+ // Create 15 emails for inactive user (should be reduced to MaxEmailsPerInactiveUser)
+ for (int i = 0; i < 15; i++)
+ {
+ var dateCreated = DateTime.UtcNow.AddDays(-i);
+ dbContext.Emails.Add(CreateTestEmail("inactiveuser@test.com", encryptionKey, $"Inactive User Email {i}", dateCreated));
+ }
+
+ await dbContext.SaveChangesAsync();
+ }
}