using Xunit; namespace LehrerApp.Api.Tests; public sealed class AttachmentStoreTests { [Fact] public async Task StoreAsync_GefolgtVonOpenRead_LiefertByteidentischeDatei() { using var temp = new TempDataPath(); var store = new AttachmentStore(temp.Path); byte[] original = [1, 2, 3, 4, 5, 255, 0, 42]; await store.StoreAsync("user-1", "abc123", new MemoryStream(original)); using var read = store.OpenRead("user-1", "abc123"); Assert.NotNull(read); using var ms = new MemoryStream(); await read!.CopyToAsync(ms); Assert.Equal(original, ms.ToArray()); } [Fact] public void OpenRead_UnbekannteStorageId_GibtNullZurueck() { using var temp = new TempDataPath(); var store = new AttachmentStore(temp.Path); Assert.Null(store.OpenRead("user-1", "unbekannt")); } [Fact] public async Task StoreAsync_TrenntAnhaengeVerschiedenerNutzer() { using var temp = new TempDataPath(); var store = new AttachmentStore(temp.Path); await store.StoreAsync("user-1", "shared-id", new MemoryStream([1])); Assert.Null(store.OpenRead("user-2", "shared-id")); using var user1Attachment = store.OpenRead("user-1", "shared-id"); Assert.NotNull(user1Attachment); } [Fact] public async Task StoreAsync_BereinigtStorageIdMitPathTraversalZeichen() { using var temp = new TempDataPath(); var store = new AttachmentStore(temp.Path); // Darf keinesfalls außerhalb von /attachments/ landen. await store.StoreAsync("user-1", "../../evil", new MemoryStream([1, 2, 3])); Assert.False(File.Exists(Path.Combine(temp.Path, "evil"))); var withinRoot = Directory.EnumerateFiles(Path.Combine(temp.Path, "attachments"), "*", SearchOption.AllDirectories); Assert.Contains(withinRoot, f => Path.GetFileName(f) == "evil"); } private sealed class TempDataPath : IDisposable { public string Path { get; } = System.IO.Path.Combine( System.IO.Path.GetTempPath(), $"lehrerapp-api-tests-attachments-{Guid.NewGuid():N}"); public TempDataPath() => Directory.CreateDirectory(Path); public void Dispose() { if (Directory.Exists(Path)) Directory.Delete(Path, recursive: true); } } }