151 lines
6.3 KiB
C#
151 lines
6.3 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Everything2Everything.App.ViewModels;
|
|
using Everything2Everything.Core;
|
|
using Everything2Everything.Core.Providers;
|
|
using Everything2Everything.Core.Filters;
|
|
using Xunit;
|
|
|
|
namespace Everything2Everything.Tests;
|
|
|
|
/// <summary>
|
|
/// 복합 사용자 유즈케이스 E2E 시나리오 TDD 테스트 스위트
|
|
/// 실제 사용자의 큐 조작, 포맷 교집합 필터링, 인코딩 옵션 튜닝, 진행률 클램프 및 취소 시나리오를 검증한다.
|
|
/// </summary>
|
|
public class E2EUserScenarioTests
|
|
{
|
|
private readonly ConversionEngine _engine;
|
|
|
|
public E2EUserScenarioTests()
|
|
{
|
|
_engine = Everything2EverythingBootstrap.CreateDefault();
|
|
}
|
|
|
|
[Fact]
|
|
public void Scenario1_FileQueue_ComputesCommonFormatIntersection_AndSortsCorrectly()
|
|
{
|
|
// 사용자가 .png 이미지와 .docx 문서를 함께 드래그 & 드롭했을 때,
|
|
// 사이드바의 출력 포맷 콤보박스는 두 입력이 "동시에 변환 가능한 출력의 교집합"만 남겨야 한다.
|
|
var pngOutputs = _engine.Providers.OutputsForInput(".png").ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
var docxOutputs = _engine.Providers.OutputsForInput(".docx").ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
// 교집합 계산
|
|
var intersection = pngOutputs.Intersect(docxOutputs, StringComparer.OrdinalIgnoreCase).ToList();
|
|
|
|
Assert.NotEmpty(intersection);
|
|
// PDF, JPG 등은 PNG와 DOCX 양쪽 모두에서 공통 출력 가능해야 함
|
|
Assert.Contains(".pdf", intersection);
|
|
Assert.Contains(".jpg", intersection);
|
|
|
|
// 만약 여기에 다른 포맷(.csv 등)이 추가되면 교집합이 변하거나 비어있어야 함
|
|
var csvOutputs = _engine.Providers.OutputsForInput(".csv").ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
var threeWayIntersection = intersection.Intersect(csvOutputs, StringComparer.OrdinalIgnoreCase).ToList();
|
|
|
|
// CSV는 텍스트/표 기반이므로 이미지/PDF와의 교집합은 없어야 함
|
|
Assert.DoesNotContain(".jpg", threeWayIntersection);
|
|
}
|
|
|
|
[Fact]
|
|
public void Scenario2_OptionsViewModel_Tuning_ReflectsInEncoderParameters()
|
|
{
|
|
// 사용자가 슬라이더와 콤보박스로 인코딩 옵션을 조정하는 시나리오
|
|
var vm = new OptionsViewModel
|
|
{
|
|
Quality = 92,
|
|
Crf = 18,
|
|
ResolutionIndex = 3, // 1080p
|
|
AudioBitrateIndex = 4, // 320 kbps
|
|
VideoPreferGpu = false
|
|
};
|
|
|
|
Assert.Equal(92, vm.Quality);
|
|
Assert.Equal(18, vm.Crf);
|
|
Assert.Equal(3, vm.ResolutionIndex);
|
|
Assert.Equal(4, vm.AudioBitrateIndex);
|
|
Assert.False(vm.VideoPreferGpu);
|
|
|
|
// ConvertOptions 에 매핑되는 로직 검증
|
|
var options = vm.ToConvertOptions();
|
|
|
|
Assert.Equal(92, options.Jpeg.Quality);
|
|
Assert.Equal(92, options.Webp.Quality);
|
|
Assert.Equal(18, options.Video.Crf);
|
|
Assert.False(options.VideoPreferGpu);
|
|
Assert.Equal(1920, options.Video.ScaleWidth);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-10.0, 0.0)]
|
|
[InlineData(0.0, 0.0)]
|
|
[InlineData(45.5, 45.5)]
|
|
[InlineData(100.0, 100.0)]
|
|
[InlineData(125.0, 100.0)]
|
|
public void Scenario3_ProgressReporting_ClampsValuesCleanlyBetween0And100(double rawValue, double expectedClamped)
|
|
{
|
|
// 백그라운드 인코더에서 비정상적인 퍼센트(-5% 또는 105%)가 전달되더라도
|
|
// UI 프로그레스 바가 깨지지 않도록 [0.0, 100.0] 범위로 정밀 클램프되어야 한다.
|
|
double clamped = Math.Clamp(rawValue, 0.0, 100.0);
|
|
Assert.Equal(expectedClamped, clamped);
|
|
}
|
|
|
|
[Fact]
|
|
public void Scenario4_ConversionGraph_FindBestPath_ProvidesValidSteps()
|
|
{
|
|
// 다단계 변환 그래프 탐색 유즈케이스 검증
|
|
var path = _engine.Providers.Graph.FindBestPath(".png", ".jpg");
|
|
Assert.NotNull(path);
|
|
Assert.NotEmpty(path);
|
|
Assert.Equal(".png", path![0].From);
|
|
}
|
|
|
|
[Fact]
|
|
public void Scenario5_MultiHopTransitive_FindBestPath_FindsValidIntermediateHops()
|
|
{
|
|
// 직접적인 단일 Provider가 없더라도 유효한 2홉 변환 경로를 찾아내는지 검증
|
|
// 예: .md -> .html -> .png
|
|
var path = _engine.Providers.Graph.FindBestPath(".md", ".png");
|
|
Assert.NotNull(path);
|
|
Assert.True(path!.Count >= 2, "MD to PNG should traverse at least 2 hops (.md -> .html/.pdf -> .png)");
|
|
Assert.Equal(".md", path[0].From);
|
|
Assert.Equal(".png", path[^1].To);
|
|
|
|
// 중간 홉이 불가능한 도메인 도약(예: .docx)을 포함하지 않는지 검증
|
|
foreach (var hop in path)
|
|
{
|
|
Assert.True(MediaConversionNegotiator.CanConvert(hop.From, hop.To),
|
|
$"Hop {hop.From} -> {hop.To} must satisfy domain negotiation rules");
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1000, 200, 80.0)] // 1000B -> 200B (80% 절감)
|
|
[InlineData(1000, 1000, 0.0)] // 동일 크기 (0% 절감)
|
|
[InlineData(1000, 1500, -50.0)] // 크기 증가 (-50% 절감)
|
|
[InlineData(0, 500, 0.0)] // 0바이트 원본 예외 방어 (DivideByZero 방어)
|
|
public void Scenario6_StorageSavingsCalculation_HandlesAllEdgeCasesSafely(long originalBytes, long outputBytes, double expectedSavings)
|
|
{
|
|
double savings = ComputeStorageSavings(originalBytes, outputBytes);
|
|
Assert.Equal(expectedSavings, savings, precision: 1);
|
|
}
|
|
|
|
private static double ComputeStorageSavings(long orig, long @out)
|
|
{
|
|
if (orig <= 0) return 0.0;
|
|
return ((double)(orig - @out) / orig) * 100.0;
|
|
}
|
|
|
|
[Fact]
|
|
public void Scenario7_MediaConversionNegotiator_StrictRejection_AcrossAllCrossDomains()
|
|
{
|
|
// 텍스트/표/오디오/이미지/비디오 간의 부적절한 도약 전수 거부 시나리오
|
|
Assert.False(MediaConversionNegotiator.CanConvert(".png", ".docx"));
|
|
Assert.False(MediaConversionNegotiator.CanConvert(".jpg", ".xlsx"));
|
|
Assert.False(MediaConversionNegotiator.CanConvert(".mp3", ".gif"));
|
|
Assert.False(MediaConversionNegotiator.CanConvert(".csv", ".mp4"));
|
|
Assert.False(MediaConversionNegotiator.CanConvert(".docx", ".mp3"));
|
|
Assert.False(MediaConversionNegotiator.CanConvert(".xlsx", ".png"));
|
|
}
|
|
|
|
}
|
|
|