using System.Runtime.CompilerServices; using BuildFlow.Shared.Domain.Errors; using BuildFlow.Shared.Domain.Exceptions; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Monitoring.Application.ExternalSystems.Commands; using Monitoring.Application.ExternalSystems.Handlers; using Monitoring.Application.Reports.Handlers; using Monitoring.Application.Reports.Queries; using Monitoring.Domain.Entities.SyncedRecord; using Monitoring.Domain.Entities.SyncJob; using Monitoring.Domain.Enums; using Monitoring.Domain.Values; using Monitoring.Infrastructure.Persistence; namespace BuildFlow.Monitoring.Tests; /// /// [SOP-MONITORING] Reports 子域 Handler 测试(GetDailyTrend / GetSyncSummary)。 /// 两者均为纯 DbContext 查询,不依赖 SyncProcessor/外部连接器,可用 InMemory 直接覆盖。 /// public class ReportsHandlerTests { private static MonitoringDbContext Db([CallerMemberName] string name = "") => MonitoringTestDb.Create(name); private static async Task SeedSystem(MonitoringDbContext db, string name = "u号租") { var created = await new CreateExternalSystemCommandHandler(db).Handle( new CreateExternalSystemCommand(name, 1, "https://x", 60, null), CancellationToken.None); return created.Id; } #region GetDailyTrend [Fact] public async Task GetDailyTrend_Empty_ReturnsEmpty() { await using var db = Db(); var sysId = await SeedSystem(db); var result = await new GetDailyTrendQueryHandler(db) .Handle(new GetDailyTrendQuery(sysId, 14), CancellationToken.None); result.Should().BeEmpty(); } [Fact] public async Task GetDailyTrend_GroupsByDay() { await using var db = Db(); var sysId = await SeedSystem(db); var today = DateTime.UtcNow.Date; db.SyncedRecords.Add(new SyncedRecordEntity { ExternalSystemId = sysId, SyncJobId = Guid.NewGuid(), ExternalId = "o1", RecordType = "order", OccurredAt = today.AddHours(1), JsonData = "{}" }); db.SyncedRecords.Add(new SyncedRecordEntity { ExternalSystemId = sysId, SyncJobId = Guid.NewGuid(), ExternalId = "o2", RecordType = "order", OccurredAt = today.AddHours(2), JsonData = "{}" }); db.SyncedRecords.Add(new SyncedRecordEntity { ExternalSystemId = sysId, SyncJobId = Guid.NewGuid(), ExternalId = "o3", RecordType = "order", OccurredAt = today.AddDays(-2), JsonData = "{}" }); await db.SaveChangesAsync(); var result = await new GetDailyTrendQueryHandler(db) .Handle(new GetDailyTrendQuery(sysId, 14), CancellationToken.None); result.Should().HaveCount(2, "今天 2 条 + 2 天前 1 条,跨 2 个日期"); result.Sum(r => r.RecordCount).Should().Be(3); } #endregion #region GetSyncSummary [Fact] public async Task GetSyncSummary_Existing_ReturnsSummary() { await using var db = Db(); var sysId = await SeedSystem(db); db.SyncJobs.Add(new SyncJobEntity { ExternalSystemId = sysId, Status = SyncJobStatusEnum.Succeeded, StartedAt = DateTime.UtcNow.AddMinutes(-5), FinishedAt = DateTime.UtcNow, RecordCount = 100, DurationMs = 1234, Data = new JobData { TriggerSource = "manual", SummaryMetrics = new() { ["order_count"] = "100" } } }); await db.SaveChangesAsync(); var result = await new GetSyncSummaryQueryHandler(db) .Handle(new GetSyncSummaryQuery(sysId, 10), CancellationToken.None); result.Should().NotBeNull(); result.ExternalSystemId.Should().Be(sysId); result.SystemName.Should().Be("u号租"); result.RecentJobs.Should().HaveCount(1); result.RecentJobs[0].Status.Should().Be("Succeeded"); result.RecentJobs[0].RecordCount.Should().Be(100); result.LatestMetrics.Should().ContainKey("order_count"); } [Fact] public async Task GetSyncSummary_NotFound_Throws() { await using var db = Db(); var act = async () => await new GetSyncSummaryQueryHandler(db) .Handle(new GetSyncSummaryQuery(Guid.NewGuid(), 10), CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.NotFound); } #endregion }