Skip to content

Commit 85ea568

Browse files
committed
Add architecture tests for UiFactory
Prevent scoped services from being injected into views
1 parent 174cff3 commit 85ea568

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using OpenBullet2.Core;
3+
using OpenBullet2.Core.Repositories;
4+
using System.Reflection;
5+
6+
namespace OpenBullet2.Native.Tests;
7+
8+
[Collection("WPF")]
9+
public sealed class UiFactoryArchitectureTests
10+
{
11+
[Fact]
12+
public void NativeViewsAndViewModels_ShouldNotInjectDbContextOrRepositories()
13+
{
14+
var uiAssembly = typeof(OpenBullet2.Native.App).Assembly;
15+
16+
var violations = uiAssembly
17+
.GetTypes()
18+
.Where(IsUiType)
19+
.SelectMany(type => type
20+
.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
21+
.SelectMany(ctor => ctor
22+
.GetParameters()
23+
.Where(parameter => IsForbidden(parameter.ParameterType))
24+
.Select(parameter =>
25+
$"{type.FullName} -> {ctor.DeclaringType?.Name}({string.Join(", ", ctor.GetParameters().Select(p => p.ParameterType.Name))}) injects {parameter.ParameterType.FullName}")))
26+
.OrderBy(message => message)
27+
.ToList();
28+
29+
Assert.True(violations.Count == 0,
30+
"UI types created from the root provider must not inject DbContext or EF-backed repositories."
31+
+ $"{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
32+
}
33+
34+
private static bool IsUiType(Type type)
35+
=> type is { IsClass: true, IsAbstract: false }
36+
&& type.Namespace is not null
37+
&& (type.Namespace.StartsWith("OpenBullet2.Native.Views", StringComparison.Ordinal)
38+
|| type.Namespace.StartsWith("OpenBullet2.Native.ViewModels", StringComparison.Ordinal));
39+
40+
private static bool IsForbidden(Type type)
41+
=> typeof(DbContext).IsAssignableFrom(type)
42+
|| ImplementsOpenGeneric(type, typeof(OpenBullet2.Core.Repositories.IRepository<>))
43+
|| type == typeof(IWordlistRepository);
44+
45+
private static bool ImplementsOpenGeneric(Type candidateType, Type openGenericType)
46+
=> candidateType.GetInterfaces()
47+
.Concat([candidateType])
48+
.Any(type => type.IsGenericType
49+
&& type.GetGenericTypeDefinition() == openGenericType);
50+
}

0 commit comments

Comments
 (0)