## 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
44 lines
1.8 KiB
C#
44 lines
1.8 KiB
C#
using System.Linq.Expressions;
|
|
using Microsoft.EntityFrameworkCore.Query;
|
|
|
|
namespace MicCheck.Api.Tests.Unit.TestSupport;
|
|
|
|
public class TestAsyncQueryProvider<T>(IQueryProvider innerProvider) : IAsyncQueryProvider
|
|
{
|
|
public IQueryable CreateQuery(Expression expression) =>
|
|
new TestAsyncEnumerable<T>(StripIncludes(expression));
|
|
|
|
public IQueryable<TElement> CreateQuery<TElement>(Expression expression) =>
|
|
new TestAsyncEnumerable<TElement>(StripIncludes(expression));
|
|
|
|
public object? Execute(Expression expression) =>
|
|
innerProvider.Execute(StripIncludes(expression));
|
|
|
|
public TResult Execute<TResult>(Expression expression) =>
|
|
innerProvider.Execute<TResult>(StripIncludes(expression));
|
|
|
|
public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
|
|
{
|
|
var stripped = StripIncludes(expression);
|
|
var resultType = typeof(TResult).GetGenericArguments() is [var inner] ? inner : typeof(TResult);
|
|
var executionResult = typeof(IQueryProvider)
|
|
.GetMethod(nameof(IQueryProvider.Execute), 1, [typeof(Expression)])!
|
|
.MakeGenericMethod(resultType)
|
|
.Invoke(innerProvider, [stripped]);
|
|
|
|
return (TResult)typeof(Task).GetMethod(nameof(Task.FromResult))!
|
|
.MakeGenericMethod(resultType)
|
|
.Invoke(null, [executionResult])!;
|
|
}
|
|
|
|
private static Expression StripIncludes(Expression expression) => new IncludeStripVisitor().Visit(expression);
|
|
|
|
private sealed class IncludeStripVisitor : ExpressionVisitor
|
|
{
|
|
protected override Expression VisitMethodCall(MethodCallExpression node) =>
|
|
node.Method.Name is "Include" or "ThenInclude"
|
|
? Visit(node.Arguments[0])
|
|
: base.VisitMethodCall(node);
|
|
}
|
|
}
|