using System.Net.Http.Json; using System.Text.Json; using FluentAssertions; using foodmarket.IntegrationTests.Support; using Xunit; namespace foodmarket.IntegrationTests; [Collection(ApiCollection.Name)] public class OrgAuditLogTests { private readonly ApiFactory _factory; public OrgAuditLogTests(ApiFactory factory) => _factory = factory; private static string RandomBarcode() => string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10))); [Fact] public async Task Product_create_writes_audit_entry() { var api = new ApiActor(_factory.CreateClient()); await api.SignupAndLoginAsync($"audit-{Guid.NewGuid():N}"); var refs = await api.LoadRefsAsync(); var pid = await api.CreateProductAsync(refs, $"AUD-{Guid.NewGuid():N}", 100m, RandomBarcode()); var log = await api.GetJsonAsync("/api/admin/audit-log?entityType=Product&pageSize=20"); var items = log.GetProperty("items").EnumerateArray().ToList(); var entry = items.First(x => x.GetProperty("entityId").GetString() == pid); entry.GetProperty("action").GetString().Should().Be("create"); entry.GetProperty("entityType").GetString().Should().Be("Product"); entry.GetProperty("changesJson").GetString().Should().Contain("\"after\""); } [Fact] public async Task Counterparty_update_diff_contains_before_after() { var api = new ApiActor(_factory.CreateClient()); await api.SignupAndLoginAsync($"audit-upd-{Guid.NewGuid():N}"); var cid = await api.CreateCounterpartyAsync($"Init-{Guid.NewGuid():N}"); // Обновляем — меняем имя. var newName = $"Renamed-{Guid.NewGuid():N}"; var resp = await api.Http.PutAsJsonAsync($"/api/catalog/counterparties/{cid}", new { name = newName, legalName = (string?)null, type = 0, bin = "987654321098", iin = (string?)null, taxNumber = (string?)null, countryId = (string?)null, address = "Алматы", phone = "+77000000000", email = "x@y.kz", bankName = (string?)null, bankAccount = (string?)null, bik = (string?)null, contactPerson = "X", notes = (string?)null, }); resp.EnsureSuccessStatusCode(); var log = await api.GetJsonAsync($"/api/admin/audit-log?entityType=Counterparty&entityId={cid}"); var items = log.GetProperty("items").EnumerateArray().ToList(); items.Should().Contain(x => x.GetProperty("action").GetString() == "update"); var update = items.First(x => x.GetProperty("action").GetString() == "update"); var diff = update.GetProperty("changesJson").GetString()!; diff.Should().Contain("\"before\""); diff.Should().Contain("\"after\""); diff.Should().Contain(newName); } [Fact] public async Task Tenant_isolation_audit_log() { var a = new ApiActor(_factory.CreateClient()); var b = new ApiActor(_factory.CreateClient()); await a.SignupAndLoginAsync($"audit-iso-a-{Guid.NewGuid():N}"); await b.SignupAndLoginAsync($"audit-iso-b-{Guid.NewGuid():N}"); var refsA = await a.LoadRefsAsync(); var pidA = await a.CreateProductAsync(refsA, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode()); // A видит свою запись. var logA = await a.GetJsonAsync($"/api/admin/audit-log?entityId={pidA}"); logA.GetProperty("items").EnumerateArray().Should().Contain( x => x.GetProperty("entityId").GetString() == pidA); // B не видит чужую запись (query-filter по OrganizationId). var logB = await b.GetJsonAsync($"/api/admin/audit-log?entityId={pidA}"); logB.GetProperty("items").EnumerateArray().Should().BeEmpty(); } }