From 16f427099519c4e437cfc2e2da60e97549d76dc9 Mon Sep 17 00:00:00 2001 From: stepan Date: Sun, 13 Sep 2026 21:45:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B2=20=D0=B0=D1=83=D0=B4=D0=B8=D1=82=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Deal.Api/Services/AuditViewFactory.cs | 27 +++++---- .../Api/AuditViewFactoryTests.cs | 59 +++++++++++++++++++ 2 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 src/core/tests/Deal.Tests.Unit/Api/AuditViewFactoryTests.cs diff --git a/src/core/Deal.Api/Services/AuditViewFactory.cs b/src/core/Deal.Api/Services/AuditViewFactory.cs index c24a679..ea53b20 100644 --- a/src/core/Deal.Api/Services/AuditViewFactory.cs +++ b/src/core/Deal.Api/Services/AuditViewFactory.cs @@ -52,33 +52,38 @@ public static class AuditViewFactory return view; } - // Заменяет идентификаторы ссылок изменения значениями из карты имён. + // Заменяет идентификаторы ссылок изменения значениями из карты имён и убирает пустые изменения. // record: Запись аудита. // references: Имена по идентификаторам ссылок. - // Возвращает: Изменения с читаемыми значениями ссылок. + // Возвращает: Изменения с читаемыми значениями ссылок без пар «без изменения». private static IReadOnlyList ResolveChanges( AuditRecordDto record, IReadOnlyDictionary references) { IReadOnlyList changes = record.AuditChanges(); - if (references.Count == 0 || changes.Count == 0) - { - return changes; - } - var resolved = new List(changes.Count); foreach (AuditChangeDto change in changes) { - resolved.Add(change with + string? from = ResolveReference(references, change.From); + string? to = ResolveReference(references, change.To); + if (IsUnchanged(from, to)) { - From = ResolveReference(references, change.From), - To = ResolveReference(references, change.To), - }); + continue; + } + + resolved.Add(change with { From = from, To = to }); } return resolved; } + // Сравнивает значения «было» и «стало». + // from: Значение до изменения. + // to: Значение после изменения. + // Возвращает: True — значения заданы и совпадают. + private static bool IsUnchanged(string? from, string? to) => + from is not null && to is not null && string.Equals(from, to, StringComparison.Ordinal); + // Подставляет имя вместо идентификатора ссылки, если оно известно. // references: Имена по идентификаторам ссылок. // value: Значение изменения. diff --git a/src/core/tests/Deal.Tests.Unit/Api/AuditViewFactoryTests.cs b/src/core/tests/Deal.Tests.Unit/Api/AuditViewFactoryTests.cs new file mode 100644 index 0000000..27f8b35 --- /dev/null +++ b/src/core/tests/Deal.Tests.Unit/Api/AuditViewFactoryTests.cs @@ -0,0 +1,59 @@ +using Deal.Api.Services; +using Deal.Modules.Tenants.Application.Models; +using Deal.Tests.Unit.Support; + +namespace Deal.Tests.Unit.Api; + +/// +/// Юнит-тесты проекции записей аудита +/// +public sealed class AuditViewFactoryTests +{ + [Fact] + public async Task Project_DropsUnchangedValues() + { + var record = new AuditRecordDto( + AuditEvents.TenantLimitChanged, + AuditActorTypes.Operator, + ActorId: null, + TenantId: null, + Ip: null, + DetailJson: """{"tenantId":"00000000-0000-0000-0000-000000000001","oldBudget":10000000,"oldPeriod":"month","budgetTokens":100000,"period":"month"}"""); + + IReadOnlyList view = await AuditViewFactory.ProjectAsync( + [record], + new TestTenantRepository().Repository, + new TestAuthStore().Store, + new TestAuditReferenceResolver(), + CancellationToken.None); + + AuditRecordViewDto row = Assert.Single(view); + AuditChangeDto budget = Assert.Single(row.Changes); + Assert.Equal("budget", budget.Field); + Assert.Equal("10000000", budget.From); + Assert.Equal("100000", budget.To); + } + + [Fact] + public async Task Project_KeepsChangedValues() + { + var record = new AuditRecordDto( + AuditEvents.SettingsUpdated, + AuditActorTypes.Tenant, + ActorId: null, + TenantId: null, + Ip: null, + DetailJson: """{"changes":[{"field":"minLen","from":"24","to":"42"}]}"""); + + IReadOnlyList view = await AuditViewFactory.ProjectAsync( + [record], + new TestTenantRepository().Repository, + new TestAuthStore().Store, + new TestAuditReferenceResolver(), + CancellationToken.None); + + AuditChangeDto change = Assert.Single(Assert.Single(view).Changes); + Assert.Equal("24", change.From); + Assert.Equal("42", change.To); + } +}