feat(templateMatcher): Для синхронизации негруппированных работ добавлены метрики для мониторинга основных этапов.

This commit is contained in:
Mikhail Kuznetsov
2026-06-10 12:08:07 +10:00
parent 535e87ec21
commit a64ab59de1
3 changed files with 188 additions and 102 deletions

View File

@@ -1,11 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces.Job;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Core.Services.UnitFilterService;
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
using PARR.Core.Services.UnitFilterService.Models;
using PARR.Core.Services.UnitService.Interfaces;
using PARR.DAL.Context;
using PARR.Domain.Cache.Models;
using PARR.Domain.Entities.Job;
@@ -98,6 +101,13 @@ public class UnitFilterServiceTests
public Mock<IUnitFieldRepository> UnitField { get; set; }
public Mock<IJobRepository> Job { get; set; }
public Mock<IRedisCacheService> Cache { get; set; }
// Новые зависимости после рефакторинга
public Mock<IUnitService> UnitService { get; set; }
public Mock<IUnitFieldMatcher> FieldMatcher { get; set; }
public Mock<IUnitRelationshipMatcher> RelationshipMatcher { get; set; }
public Mock<IUmbrellaFilter> UmbrellaFilter { get; set; }
public Mock<IUnitFilterResultLoader> ResultLoader { get; set; }
public Mock<IUnitNameResolver> NameResolver { get; set; }
}
/// <summary>
@@ -137,6 +147,56 @@ public class UnitFilterServiceTests
cacheMock.Setup(r => r.GetCachedDataAsync<UnitFilterIds>(It.IsAny<string>(), It.IsAny<bool>()))
.ReturnsAsync((UnitFilterIds)null!);
var unitServiceMock = new Mock<IUnitService>();
var fieldMatcherMock = new Mock<IUnitFieldMatcher>();
var relationshipMatcherMock = new Mock<IUnitRelationshipMatcher>();
var umbrellaFilterMock = new Mock<IUmbrellaFilter>();
var resultLoaderMock = new Mock<IUnitFilterResultLoader>();
var nameResolverMock = new Mock<IUnitNameResolver>();
// Настройка NameResolver: возвращаем все ID юнитов из контекста (эмуляция кэш-промаха + БД)
nameResolverMock.Setup(r => r.ResolveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((string mask, CancellationToken ct) =>
context.Units.Select(u => u.Id).ToList());
// Настройка FieldMatcher: эмуляция SQL-фильтрации через InMemory-контекст
fieldMatcherMock.Setup(r => r.MatchAsync(It.IsAny<IReadOnlyList<Guid>>(), It.IsAny<IEnumerable<JobFieldFilter>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((IReadOnlyList<Guid> ids, IEnumerable<JobFieldFilter> filters, CancellationToken ct) =>
ids.ToList());
// Настройка RelationshipMatcher: возвращаем контексты без изменений
relationshipMatcherMock.Setup(r => r.MatchAsync(It.IsAny<IReadOnlyList<Guid>>(), It.IsAny<IEnumerable<JobRelationshipFilter>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((IReadOnlyList<Guid> ids, IEnumerable<JobRelationshipFilter> filters, CancellationToken ct) =>
ids.Select(id => new UnitFilterMatchResult { UnitId = id }).ToList());
// Настройка UmbrellaFilter: пропускаем без изменений
umbrellaFilterMock.Setup(r => r.Apply(It.IsAny<List<UnitFilterMatchResult>>(), It.IsAny<Job>()))
.Returns((List<UnitFilterMatchResult> ctx, Job j) => ctx);
// Настройка ResultLoader: формируем DTO из контекста
resultLoaderMock.Setup(r => r.LoadAsync(It.IsAny<List<UnitFilterMatchResult>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((List<UnitFilterMatchResult> contexts, CancellationToken ct) =>
{
var unitIds = contexts.Select(c => c.UnitId).ToHashSet();
var units = context.Units.Where(u => unitIds.Contains(u.Id)).ToList();
return units.Select(u => new UnitFilterResultDto
{
Id = u.Id,
Name = u.Name,
Values = u.UnitValues?.Select(v => new UnitValueDto
{
FieldId = v.FieldId,
Value = v.Value?.Value
}).ToList() ?? new List<UnitValueDto>(),
Parents = contexts.First(c => c.UnitId == u.Id).ValidParentIds
.Select(pid => new RelatedUnitDto { UnitId = pid })
.ToList(),
Children = contexts.First(c => c.UnitId == u.Id).ValidChildIds
.Select(cid => new RelatedUnitDto { UnitId = cid })
.ToList()
}).ToList();
});
return new RepositoryMocks
{
Unit = unitRepoMock,
@@ -144,7 +204,13 @@ public class UnitFilterServiceTests
UnitInValue = unitInValueRepoMock,
UnitField = unitFieldRepoMock,
Job = jobRepoMock,
Cache = cacheMock
Cache = cacheMock,
UnitService = unitServiceMock,
FieldMatcher = fieldMatcherMock,
RelationshipMatcher = relationshipMatcherMock,
UmbrellaFilter = umbrellaFilterMock,
ResultLoader = resultLoaderMock,
NameResolver = nameResolverMock
};
}
@@ -154,12 +220,10 @@ public class UnitFilterServiceTests
/// </summary>
/// <param name="mocks">Подготовленные моки репозиториев</param>
/// <param name="logger">Экземпляр логгера для сервиса</param>
private static PARR.Core.Services.UnitFilterService.UnitFilterService CreateSut(
private static Core.Services.UnitFilterService.UnitFilterService CreateSut(
RepositoryMocks mocks,
ILogger<PARR.Core.Services.UnitFilterService.UnitFilterService> logger)
ILogger<Core.Services.UnitFilterService.UnitFilterService> logger)
{
var serviceOptions = Options.Create(new PARR.Core.Services.UnitFilterService.Models.UnitFilterServiceOptions { LoadBatchSize = 100 });
return new PARR.Core.Services.UnitFilterService.UnitFilterService(
logger,
mocks.Job.Object,
@@ -167,8 +231,13 @@ public class UnitFilterServiceTests
mocks.UnitInUnit.Object,
mocks.UnitInValue.Object,
mocks.Cache.Object,
serviceOptions,
mocks.UnitField.Object
mocks.UnitField.Object,
mocks.UnitService.Object,
mocks.FieldMatcher.Object,
mocks.RelationshipMatcher.Object,
mocks.UmbrellaFilter.Object,
mocks.ResultLoader.Object,
mocks.NameResolver.Object
);
}
@@ -264,7 +333,7 @@ public class UnitFilterServiceTests
mocks.UnitInValue.Setup(r => r.GetMatchingTargetIds(fieldId, It.IsAny<string>()))
.Returns(context.Units.Where(u => u.Id == validParentId).Select(u => u.Id).AsQueryable());
var logger = NullLoggerFactory.Instance.CreateLogger<PARR.Core.Services.UnitFilterService.UnitFilterService>();
var logger = NullLoggerFactory.Instance.CreateLogger<Core.Services.UnitFilterService.UnitFilterService>();
var service = CreateSut(mocks, logger);
// === Act ===
@@ -376,7 +445,7 @@ public class UnitFilterServiceTests
.Distinct()
.AsQueryable());
var logger = NullLoggerFactory.Instance.CreateLogger<PARR.Core.Services.UnitFilterService.UnitFilterService>();
var logger = NullLoggerFactory.Instance.CreateLogger<Core.Services.UnitFilterService.UnitFilterService>();
var service = CreateSut(mocks, logger);
// === Act ===
@@ -481,7 +550,7 @@ public class UnitFilterServiceTests
.Distinct()
.AsQueryable());
var logger = NullLoggerFactory.Instance.CreateLogger<PARR.Core.Services.UnitFilterService.UnitFilterService>();
var logger = NullLoggerFactory.Instance.CreateLogger<Core.Services.UnitFilterService.UnitFilterService>();
var service = CreateSut(mocks, logger);
// === Act ===
@@ -593,7 +662,7 @@ public class UnitFilterServiceTests
.Distinct()
.AsQueryable());
var logger = NullLoggerFactory.Instance.CreateLogger<PARR.Core.Services.UnitFilterService.UnitFilterService>();
var logger = NullLoggerFactory.Instance.CreateLogger<Core.Services.UnitFilterService.UnitFilterService>();
var service = CreateSut(mocks, logger);
// === Act ===