1
0
Fork 0

refactor(P2): DI 컴포지션 루트 + Scrutor 어셈블리 스캔 자동등록

컴파일타임 하드코딩(Bootstrap 14개 new + 수동 생성자 와이어링)을 제거 — 새 Provider는 클래스만
추가하면 자동 등록되고 그래프가 변환 엣지를 자동 흡수(확장성 토대). Strangler Fig 파사드로 호출부 무변경.

- Core/ServiceCollectionExtensions.AddEverything2Everything: Scrutor FromAssembliesOf →
  AssignableTo<IConverterProvider> → AsSelfWithInterfaces(단일 싱글턴 공유) → Singleton.
  Heic(Magick)/Docx·Ocr·Hwpx(Pdf)/Llm(Settings) 생성자 의존 자동 해소. ProviderRegistry/Engine 등록.
- Bootstrap.CreateDefault: 내부를 DI(BuildServiceProvider→GetRequiredService)로 교체하되
  시그니처·동작 불변 — App/CLI/테스트 호출부 전부 무파손.
- 패키지: Microsoft.Extensions.DependencyInjection 9.0.0, Scrutor 4.2.2 (CPM 경유).
- DependencyInjectionTests: 14개 전수 해소 + 생성자 의존 자동 와이어링 + AsSelf 동일 인스턴스
  + 공유 Settings + 파사드 하위호환 검증(적대적 리뷰의 'AsSelf 누락 silent 실패' 방어).

App.xaml.cs Generic Host 전환·서비스로케이터 제거는 GUI 스모크가 필요해 P5(MVVM 생성자 주입)로 통합.
69개 테스트 전부 그린(65+4), 빌드 0경고/0오류, 골든마스터 녹색 유지(동작 불변).
This commit is contained in:
Yun Chan 2026-06-01 23:30:06 +09:00
parent 6b524662a4
commit 8419bf30ff
5 changed files with 126 additions and 22 deletions

View file

@ -0,0 +1,71 @@
using System.Linq;
using Everything2Everything.Core;
using Everything2Everything.Core.Converters;
using Everything2Everything.Core.Providers;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Everything2Everything.Tests;
/// <summary>
/// DI 컴포지션 루트(AddEverything2Everything) 등록 규약 검증. Scrutor 자동 등록이 하드코딩 14개와
/// 동일하게 모든 Provider를 해소하고, 생성자 의존(Magick/Pdf/Settings)이 자동 와이어링되는지 보증한다.
/// 적대적 리뷰 지적: AsSelf 누락 시 Heic/Docx/Ocr 의존 해소가 silent 실패 → 일부 Provider 런타임 누락.
/// </summary>
public class DependencyInjectionTests
{
[Fact]
public void AddEverything2Everything_RegistersAll14Providers()
{
var services = new ServiceCollection();
services.AddEverything2Everything();
using var sp = services.BuildServiceProvider();
var providers = sp.GetServices<IConverterProvider>().ToList();
Assert.Equal(14, providers.Count); // 하드코딩 14개와 동일 — Scrutor 누락/초과 방어
Assert.NotNull(sp.GetRequiredService<ProviderRegistry>());
Assert.NotNull(sp.GetRequiredService<ConversionEngine>());
}
[Fact]
public void ConstructorDependencies_AreAutoWired()
{
var services = new ServiceCollection();
services.AddEverything2Everything();
using var sp = services.BuildServiceProvider();
// 이들이 해소되면 생성자 의존이 DI로 연결된 것(AsSelf 미등록 시 여기서 실패).
Assert.NotNull(sp.GetRequiredService<HeicProvider>()); // ← MagickProvider 주입
Assert.NotNull(sp.GetRequiredService<DocxProvider>()); // ← PdfProvider 주입
Assert.NotNull(sp.GetRequiredService<OcrProvider>()); // ← PdfProvider 주입
Assert.NotNull(sp.GetRequiredService<HwpxProvider>()); // ← PdfProvider 주입
Assert.NotNull(sp.GetRequiredService<LlmProvider>()); // ← ISettingsStore 주입
// AsSelfWithInterfaces: 구체 타입과 인터페이스가 동일 싱글턴 인스턴스를 공유.
var magickAsSelf = sp.GetRequiredService<MagickProvider>();
var magickAsInterface = sp.GetServices<IConverterProvider>().OfType<MagickProvider>().Single();
Assert.Same(magickAsSelf, magickAsInterface);
}
[Fact]
public void SharedSettings_AreRegisteredInstance()
{
// App이 공유 ISettingsStore를 주입하면 그 인스턴스가 등록되어야 한다(LlmProvider 키 공유).
var settings = new DpapiSettingsStore();
var services = new ServiceCollection();
services.AddEverything2Everything(settings);
using var sp = services.BuildServiceProvider();
Assert.Same(settings, sp.GetRequiredService<ISettingsStore>());
}
[Fact]
public void CreateDefault_Facade_StillWorks()
{
// 파사드 하위호환: DI 내부전환 후에도 CreateDefault가 동일하게 엔진/그래프를 구성.
var engine = Everything2EverythingBootstrap.CreateDefault();
Assert.Equal(14, engine.Providers.All.Count);
Assert.NotNull(engine.Providers.Graph.FindBestPath(".png", ".jpg"));
Assert.NotNull(engine.Providers.Graph.FindBestPath(".svg", ".jpg", maxHops: 3));
}
}