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); } }