using Microsoft.AspNetCore.Http; using WxAgent.Service; using Xunit; namespace WxAgent.Service.Tests; public sealed class ArtifactStoreTests { [Fact] public async Task ArtifactIdsAreOpaqueOwnedBoundedAndPathSafe() { var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); var options = new ServiceOptions { DataDirectory = dir, CredentialFile = "unused" }; try { var store = new ArtifactStore(options); await using var body = new MemoryStream("safe"u8.ToArray()); var file = new FormFile(body, 0, body.Length, "file", "note.txt") { Headers = new HeaderDictionary(), ContentType = "text/plain" }; var info = await store.SaveAsync("alice", file, default); Assert.Matches("^[0-9a-f]{32}$", info.ArtifactId); Assert.Equal("alice", store.Get("alice", info.ArtifactId).PrincipalId); Assert.Throws(() => store.Get("bob", info.ArtifactId)); await using var opened = store.Open("alice", info.ArtifactId); using var reader = new StreamReader(opened); Assert.Equal("safe", await reader.ReadToEndAsync()); Assert.Throws(() => store.Get("alice", "../secrets")); await using var badBody = new MemoryStream("x"u8.ToArray()); var bad = new FormFile(badBody, 0, badBody.Length, "file", "payload.exe") { Headers = new HeaderDictionary(), ContentType = "application/octet-stream" }; Assert.Equal("FileTypeRejected", (await Assert.ThrowsAsync(() => store.SaveAsync("alice", bad, default))).Code); } finally { Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); Directory.Delete(dir, true); } } }