using LehrerApp.Data; using LehrerApp.Sync.Crypto; using Xunit; namespace LehrerApp.Sync.Tests; public sealed class AttachmentSyncerTests { private static LiteDbContext NewInMemoryContext() => new(new MemoryStream()); private static readonly byte[] Key = SyncCrypto.GenerateKey(); [Fact] public async Task UploadPendingAsync_LaedtVerschluesselteBytesHochUndMarkiertAlsErledigt() { using var temp = new TempEventQueue(); using var db = NewInMemoryContext(); var storageId = Guid.NewGuid().ToString("N"); db.Attachments.Upload(storageId, "brief.pdf", new MemoryStream([1, 2, 3, 4])); temp.Queue.QueueAttachmentUpload(storageId); // Bytes MÜSSEN synchron innerhalb des Handler-Callbacks gelesen werden: AttachmentSyncer // disposed sein ByteArrayContent direkt nach dem PostAsync-Aufruf (eigenes "using"), // ein Zugriff auf request.Content danach würde ObjectDisposedException werfen. byte[]? uploadedEncrypted = null; var handler = new FakeHttpMessageHandler(req => { uploadedEncrypted = req.Content!.ReadAsByteArrayAsync().GetAwaiter().GetResult(); return new HttpResponseMessage(System.Net.HttpStatusCode.OK); }); var http = new HttpClient(handler) { BaseAddress = new Uri("https://example.invalid") }; var syncer = new AttachmentSyncer(db, http, Key); await syncer.UploadPendingAsync(temp.Queue); var request = Assert.Single(handler.Requests); Assert.Equal(HttpMethod.Post, request.Method); Assert.Equal($"/api/sync/attachments/{storageId}", request.RequestUri!.AbsolutePath); Assert.NotNull(uploadedEncrypted); Assert.Equal([1, 2, 3, 4], SyncCrypto.Decrypt(uploadedEncrypted!, Key)); Assert.Empty(temp.Queue.GetPendingAttachmentUploads()); } [Fact] public async Task UploadPendingAsync_LokalNichtMehrVorhandenerAnhang_WirdOhneUploadAlsErledigtMarkiert() { using var temp = new TempEventQueue(); using var db = NewInMemoryContext(); temp.Queue.QueueAttachmentUpload("laengst-geloescht"); var handler = new FakeHttpMessageHandler(_ => new HttpResponseMessage(System.Net.HttpStatusCode.OK)); var http = new HttpClient(handler) { BaseAddress = new Uri("https://example.invalid") }; var syncer = new AttachmentSyncer(db, http, Key); await syncer.UploadPendingAsync(temp.Queue); Assert.Empty(handler.Requests); Assert.Empty(temp.Queue.GetPendingAttachmentUploads()); } private sealed class TempEventQueue : IDisposable { private readonly string _directory = Path.Combine( Path.GetTempPath(), $"lehrerapp-sync-tests-attachments-{Guid.NewGuid():N}"); public EventQueue Queue { get; } public TempEventQueue() { Directory.CreateDirectory(_directory); Queue = new EventQueue(Path.Combine(_directory, "queue.db")); } public void Dispose() { Queue.Dispose(); if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true); } } }