using GerbilManagerWebAPI.Inbox;
using GerbilManagerWebAPI.Models;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
namespace GerbilManager.Tests;
/// INBOX-0: sync dedup + idempotency + config-guard, against an in-memory DB and a
/// fake IGmailMailReader (the live Gmail/MailKit path is gated on Julian's App Password).
public class RequestSyncServiceTests
{
private static ApplicationContext NewDb()
{
var opts = new DbContextOptionsBuilder()
.UseInMemoryDatabase("inbox-" + Guid.NewGuid().ToString("N")).Options;
var db = new ApplicationContext(opts);
db.Database.EnsureCreated(); // seeds the MailSettings singleton
return db;
}
private sealed class FakeReader : IGmailMailReader
{
public List Canned { get; set; } = new();
public Task> FetchAsync(MailConnection c, uint sinceUid, CancellationToken ct = default)
=> Task.FromResult((IReadOnlyList)Canned.Where(m => m.Uid > sinceUid).ToList());
}
private static MailSummary M(uint uid, string msgId, string from) =>
new(msgId, ThreadId: null, InReplyTo: null, References: null,
FromAddress: from, FromName: null, Subject: "Anfrage", BodyText: "Hallo",
ReceivedAt: DateTimeOffset.UtcNow, Uid: uid);
private static async Task<(ApplicationContext db, RequestSyncService sync, FakeReader reader)> SetupAsync(bool configured = true)
{
var db = NewDb();
var settings = new MailSettingsService(db, new EphemeralDataProtectionProvider());
var s = await settings.GetAsync();
if (configured)
{
s.GmailAddress = "zucht@gmail.com";
settings.SetPassword(s, "app-pw-1234");
await db.SaveChangesAsync();
}
var reader = new FakeReader();
return (db, new RequestSyncService(db, reader, settings), reader);
}
[Fact]
public async Task Sync_imports_new_and_dedups_within_batch()
{
var (db, sync, reader) = await SetupAsync();
reader.Canned = new() { M(1, "", "a@x.de"), M(2, "", "b@y.de"), M(3, "", "b@y.de") };
var res = await sync.SyncAsync();
Assert.Null(res.Error);
Assert.Equal(2, res.Imported); // deduped
Assert.Equal(2, await db.Requests.CountAsync());
Assert.All(await db.Requests.ToListAsync(), r => Assert.Equal(RequestStatus.New, r.Status));
}
[Fact]
public async Task Resync_imports_nothing_new()
{
var (db, sync, reader) = await SetupAsync();
reader.Canned = new() { M(1, "", "a@x.de"), M(2, "", "b@y.de") };
await sync.SyncAsync();
var second = await sync.SyncAsync(); // LastUid advanced -> reader returns nothing newer
Assert.Equal(0, second.Imported);
Assert.Equal(2, await db.Requests.CountAsync());
}
[Fact]
public async Task Sync_without_config_reports_not_configured()
{
var (_, sync, reader) = await SetupAsync(configured: false);
reader.Canned = new() { M(1, "", "a@x.de") };
var res = await sync.SyncAsync();
Assert.Equal("MailNotConfigured", res.Error);
Assert.Equal(0, res.Imported);
}
}