Files
mic-check/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureUsageQueryServiceTests.cs
James Wampler 20188c61a2
All checks were successful
CI / build-and-push (push) Successful in 48s
CI / deploy-qa (push) Successful in 12s
CI / smoke-qa (push) Successful in 20s
Refactor nunit tests off InMemory/WebApplicationFactory, remove tuple returns (#4)
## Summary
- Extract `IMicCheckDbContext` and mock DB access with Moq instead of the EF InMemory provider, per CLAUDE.md testing guidelines.
- Rewrite HTTP-pipeline tests to exercise controllers/auth handlers directly instead of `WebApplicationFactory`.
- Remove tuple return types across the API in favor of named records (`PagedResult<T>`, `ProfileUpdateResult`, `ApiKeyCreationResult`).

## Test plan
- [x] `dotnet build` (full solution)
- [x] `dotnet test tests/api/MicCheck.Api.Tests.Unit` — 187/187 pass
- [x] `dotnet test tests/api/MicCheck.Api.Tests.Integration` — 8/8 pass (live API + Postgres via Aspire)
- [x] Verified Aspire AppHost/dashboard starts and API responds

Reviewed-on: #4
2026-07-04 21:40:47 -07:00

136 lines
5.2 KiB
C#

using MicCheck.Api.Data;
using MicCheck.Api.Features;
using MicCheck.Api.Tests.Unit.TestSupport;
using Moq;
using NUnit.Framework;
namespace MicCheck.Api.Tests.Unit.Features;
[TestFixture]
public class FeatureUsageQueryServiceTests
{
private Mock<IMicCheckDbContext> _db = null!;
private List<FeatureUsageDaily> _usage = null!;
private FeatureUsageQueryService _service = null!;
private const int EnvironmentId = 1;
[SetUp]
public void SetUp()
{
_db = new Mock<IMicCheckDbContext>();
_usage = [];
_db.SetupDbSet(c => c.FeatureUsageDaily, _usage);
_service = new FeatureUsageQueryService(_db.Object);
}
private void SeedUsage(int environmentId, int featureId, string featureName, DateOnly date, long count)
{
_usage.Add(new FeatureUsageDaily
{
EnvironmentId = environmentId,
FeatureId = featureId,
FeatureName = featureName,
UsageDate = date,
Count = count,
UpdatedAt = DateTimeOffset.UtcNow
});
}
[Test]
public async Task WhenNoUsageData_ThenEmptyCollectionsAreReturned()
{
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 14);
Assert.That(result.TopFeaturesLastDay, Is.Empty);
Assert.That(result.DailyUsage, Is.Empty);
}
[Test]
public async Task WhenMultipleFeaturesEvaluatedToday_ThenTopFeaturesLastDayOrdersByCountDescending()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
SeedUsage(EnvironmentId, featureId: 1, "feature_a", today, count: 50);
SeedUsage(EnvironmentId, featureId: 2, "feature_b", today, count: 200);
SeedUsage(EnvironmentId, featureId: 3, "feature_c", today, count: 10);
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 1);
Assert.That(result.TopFeaturesLastDay, Has.Count.EqualTo(3));
Assert.That(result.TopFeaturesLastDay[0].FeatureName, Is.EqualTo("feature_b"));
Assert.That(result.TopFeaturesLastDay[0].Count, Is.EqualTo(200));
Assert.That(result.TopFeaturesLastDay[1].FeatureName, Is.EqualTo("feature_a"));
Assert.That(result.TopFeaturesLastDay[2].FeatureName, Is.EqualTo("feature_c"));
}
[Test]
public async Task WhenMoreThanTenFeatures_ThenTopFeaturesLastDayLimitsToTen()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
for (var i = 1; i <= 15; i++)
SeedUsage(EnvironmentId, featureId: i, $"feature_{i}", today, count: i * 10L);
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 1);
Assert.That(result.TopFeaturesLastDay, Has.Count.EqualTo(10));
}
[Test]
public async Task WhenUsageSpansMultipleDays_ThenDailyUsageGroupsByDateAscending()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
SeedUsage(EnvironmentId, featureId: 1, "flag", today.AddDays(-2), count: 30);
SeedUsage(EnvironmentId, featureId: 1, "flag", today.AddDays(-1), count: 50);
SeedUsage(EnvironmentId, featureId: 1, "flag", today, count: 20);
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 7);
Assert.That(result.DailyUsage, Has.Count.EqualTo(3));
Assert.That(result.DailyUsage[0].Date, Is.EqualTo(today.AddDays(-2)));
Assert.That(result.DailyUsage[0].TotalCount, Is.EqualTo(30));
Assert.That(result.DailyUsage[1].TotalCount, Is.EqualTo(50));
Assert.That(result.DailyUsage[2].TotalCount, Is.EqualTo(20));
}
[Test]
public async Task WhenHoveringADay_ThenDailyUsageIncludesPerFeatureBreakdownOrderedByCount()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
SeedUsage(EnvironmentId, featureId: 1, "feature_a", today, count: 10);
SeedUsage(EnvironmentId, featureId: 2, "feature_b", today, count: 40);
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 1);
var day = result.DailyUsage.Single();
Assert.That(day.TotalCount, Is.EqualTo(50));
Assert.That(day.Features, Has.Count.EqualTo(2));
Assert.That(day.Features[0].FeatureName, Is.EqualTo("feature_b"));
Assert.That(day.Features[0].Count, Is.EqualTo(40));
Assert.That(day.Features[1].FeatureName, Is.EqualTo("feature_a"));
}
[Test]
public async Task WhenUsageExistsOutsideDaysWindow_ThenOldDataIsExcluded()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
SeedUsage(EnvironmentId, featureId: 1, "flag", today.AddDays(-30), count: 999);
SeedUsage(EnvironmentId, featureId: 1, "flag", today.AddDays(-3), count: 5);
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 7);
Assert.That(result.DailyUsage, Has.Count.EqualTo(1));
Assert.That(result.DailyUsage[0].TotalCount, Is.EqualTo(5));
}
[Test]
public async Task WhenOtherEnvironmentHasUsage_ThenItIsNotIncludedInResults()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
SeedUsage(environmentId: 99, featureId: 1, "flag", today, count: 100);
var result = await _service.GetDashboardUsageAsync(EnvironmentId, days: 7);
Assert.That(result.TopFeaturesLastDay, Is.Empty);
Assert.That(result.DailyUsage, Is.Empty);
}
}