Files
mic-check/src/api/MicCheck.Api/Features/FeatureSegmentService.cs
James Wampler b4a666ebf0 Refactor nunit tests off EF InMemory provider and web server
Extract IMicCheckDbContext so DB access can be mocked with Moq instead
of the InMemory provider, per project testing guidelines. Add shared
async DbSet test doubles (TestSupport/) to back the mocks. Rewrite
HTTP-pipeline tests (WebApplicationFactory) to exercise controllers
and auth handlers directly instead of running the web server. Drop
DbContext/seeder tests that only made sense against a real provider,
and remove stale duplicate test files left over from a namespace move.
2026-07-04 19:54:22 -07:00

125 lines
4.6 KiB
C#
Executable File

using MicCheck.Api.Data;
using MicCheck.Api.Segments;
using Microsoft.EntityFrameworkCore;
namespace MicCheck.Api.Features;
public class FeatureSegmentService(IMicCheckDbContext db)
{
public async Task<IReadOnlyList<FeatureSegmentResponse>> ListByFeatureAsync(
int featureId, int environmentId, CancellationToken ct = default)
{
var featureSegments = await db.FeatureSegments
.Where(fsg => fsg.FeatureId == featureId && fsg.EnvironmentId == environmentId)
.OrderBy(fsg => fsg.Priority)
.ToListAsync(ct);
var segmentIds = featureSegments.Select(fsg => fsg.SegmentId).Distinct().ToList();
var segments = await db.Segments
.Where(s => segmentIds.Contains(s.Id))
.ToDictionaryAsync(s => s.Id, ct);
var states = await db.FeatureStates
.Where(fs => fs.FeatureId == featureId && fs.EnvironmentId == environmentId && fs.FeatureSegmentId != null)
.ToListAsync(ct);
var stateBySegment = states.ToDictionary(fs => fs.FeatureSegmentId!.Value);
return featureSegments.Select(fsg =>
{
var segmentName = segments.TryGetValue(fsg.SegmentId, out var seg) ? seg.Name : "Unknown";
stateBySegment.TryGetValue(fsg.Id, out var state);
return new FeatureSegmentResponse(
fsg.Id, fsg.FeatureId, fsg.SegmentId, segmentName,
fsg.EnvironmentId, fsg.Priority,
state?.Enabled, state?.Value);
}).ToList();
}
public async Task<FeatureSegmentResponse> CreateAsync(
int featureId, int environmentId, int segmentId, int priority, bool enabled, string? value,
CancellationToken ct = default)
{
var segment = await db.Segments.FirstOrDefaultAsync(s => s.Id == segmentId, ct)
?? throw new KeyNotFoundException($"Segment {segmentId} not found.");
var featureSegment = new FeatureSegment
{
FeatureId = featureId,
SegmentId = segmentId,
EnvironmentId = environmentId,
Priority = priority
};
db.FeatureSegments.Add(featureSegment);
await db.SaveChangesAsync(ct);
var state = new FeatureState
{
FeatureId = featureId,
EnvironmentId = environmentId,
FeatureSegmentId = featureSegment.Id,
Enabled = enabled,
Value = value,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
};
db.FeatureStates.Add(state);
await db.SaveChangesAsync(ct);
return new FeatureSegmentResponse(
featureSegment.Id, featureId, segmentId, segment.Name,
environmentId, priority, enabled, value);
}
public async Task<FeatureSegmentResponse?> UpdateAsync(
int id, int priority, bool enabled, string? value, CancellationToken ct = default)
{
var featureSegment = await db.FeatureSegments.FirstOrDefaultAsync(fsg => fsg.Id == id, ct);
if (featureSegment is null) return null;
var segment = await db.Segments.FirstOrDefaultAsync(s => s.Id == featureSegment.SegmentId, ct);
featureSegment.Priority = priority;
var state = await db.FeatureStates
.FirstOrDefaultAsync(fs => fs.FeatureSegmentId == id, ct);
if (state is not null)
{
state.Enabled = enabled;
state.Value = value;
state.UpdatedAt = DateTimeOffset.UtcNow;
}
else
{
state = new FeatureState
{
FeatureId = featureSegment.FeatureId,
EnvironmentId = featureSegment.EnvironmentId,
FeatureSegmentId = featureSegment.Id,
Enabled = enabled,
Value = value,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
};
db.FeatureStates.Add(state);
}
await db.SaveChangesAsync(ct);
return new FeatureSegmentResponse(
featureSegment.Id, featureSegment.FeatureId, featureSegment.SegmentId,
segment?.Name ?? "Unknown", featureSegment.EnvironmentId,
priority, enabled, value);
}
public async Task<bool> DeleteAsync(int id, CancellationToken ct = default)
{
var featureSegment = await db.FeatureSegments.FirstOrDefaultAsync(fsg => fsg.Id == id, ct);
if (featureSegment is null) return false;
db.FeatureSegments.Remove(featureSegment);
await db.SaveChangesAsync(ct);
return true;
}
}