Files
cesnimda 2b19bddf7b
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 11s
Deploy Staging / deploy (push) Successful in 24s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 52s
CI / backend (pull_request) Successful in 49s
CI / frontend (pull_request) Successful in 10s
Security / secrets (pull_request) Successful in 3s
Security / dependencies (pull_request) Successful in 55s
feat(security): audit batch C — data posture, retention, DP keys (#22)
2026-07-02 10:13:07 +02:00

74 lines
2.9 KiB
C#

using FluentAssertions;
using InboxIntel.Application.Abstractions;
using InboxIntel.Domain.Entities;
using InboxIntel.Infrastructure.Configuration;
using InboxIntel.Infrastructure.Persistence;
using InboxIntel.Infrastructure.Retention;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace InboxIntel.IntegrationTests;
/// <summary>
/// AUDIT H-3 (retention): the purge removes only what the configuration targets and is a
/// no-op while disabled — locking the "no behaviour change until opted in" guarantee.
/// </summary>
public class RetentionServiceTests
{
private sealed class FakeCurrentUser : ICurrentUser
{
public Guid UserId => Guid.Empty; // worker scope
public bool IsAuthenticated => false;
}
private static AppDbContext NewDb(string name) =>
new(new DbContextOptionsBuilder<AppDbContext>().UseInMemoryDatabase(name).Options, new FakeCurrentUser());
private static async Task SeedAsync(AppDbContext db)
{
var user = Guid.NewGuid();
db.Emails.AddRange(
new Email { UserId = user, GmailMessageId = "old-trashed", IsTrashed = true, SentAtUtc = DateTimeOffset.UtcNow.AddDays(-100) },
new Email { UserId = user, GmailMessageId = "new-trashed", IsTrashed = true, SentAtUtc = DateTimeOffset.UtcNow.AddDays(-5) },
new Email { UserId = user, GmailMessageId = "old-kept", IsTrashed = false, SentAtUtc = DateTimeOffset.UtcNow.AddDays(-100) });
await db.SaveChangesAsync();
}
private static RetentionService Sut(AppDbContext db, int trashedDays = 0, int allDays = 0) =>
new(db, Options.Create(new DataRetentionOptions
{
PurgeTrashedAfterDays = trashedDays,
PurgeAllAfterDays = allDays
}), NullLogger<RetentionService>.Instance);
[Fact]
public async Task Disabled_retention_purges_nothing()
{
using var db = NewDb(nameof(Disabled_retention_purges_nothing));
await SeedAsync(db);
(await Sut(db).PurgeAsync()).Should().Be(0);
(await db.Emails.CountAsync()).Should().Be(3);
}
[Fact]
public async Task Trashed_purge_removes_only_old_trashed_emails()
{
using var db = NewDb(nameof(Trashed_purge_removes_only_old_trashed_emails));
await SeedAsync(db);
(await Sut(db, trashedDays: 30).PurgeAsync()).Should().Be(1);
var remaining = await db.Emails.Select(e => e.GmailMessageId).ToListAsync();
remaining.Should().BeEquivalentTo(new[] { "new-trashed", "old-kept" });
}
[Fact]
public async Task Age_purge_removes_everything_past_the_cutoff()
{
using var db = NewDb(nameof(Age_purge_removes_everything_past_the_cutoff));
await SeedAsync(db);
(await Sut(db, allDays: 30).PurgeAsync()).Should().Be(2); // both 100-day-old emails
(await db.Emails.SingleAsync()).GmailMessageId.Should().Be("new-trashed");
}
}