using System.Windows; using System.Windows.Threading; using Microsoft.Web.WebView2.Core; using VideoDownloader.Browser; using VideoDownloader.Tests.Infrastructure; namespace VideoDownloader.Tests.E2E; /// /// E2E (T3-11/T3-14): 실제 WebView2 에서 권한 정책 응답과 추적 요청 차단·집계를 검증. /// TabViewService 와 동일한 배선(PermissionGate/TrackerBlocklist/TrackerCounter)을 사용한다. /// [Collection("E2E")] public class PermissionTrackerE2ETests { [Fact] public async Task 권한_요청이_정책을_거쳐_허용된다_T3_11() { using var sta = StaDispatcher.Start(); var udf = Path.Combine(Path.GetTempPath(), $"vd_e2e_{Guid.NewGuid():N}"); var result = await sta.InvokeAsync(async () => { var window = new Window(); var browser = new Microsoft.Web.WebView2.Wpf.WebView2(); window.Content = browser; window.Show(); var env = await CoreWebView2Environment.CreateAsync(userDataFolder: udf); await browser.EnsureCoreWebView2Async(env); var cv = browser.CoreWebView2; // TabViewService 배선과 동일: 기억된 판정은 즉시 응답(출처는 이벤트에서 관측된 것으로 기억) var gate = new PermissionGate(); cv.PermissionRequested += (_, e) => { var o = VideoDownloader.App.Services.TabViewService.OriginOf(e.Uri); gate.Remember("Notifications", o, PermissionVerdict.Allow); // E2E: 알림은 허용 정책 var verdict = gate.Decide(e.PermissionKind.ToString(), o); e.State = verdict == PermissionVerdict.Allow ? CoreWebView2PermissionState.Allow : CoreWebView2PermissionState.Deny; e.Handled = true; }; var html = ""; cv.NavigateToString(html); await WaitUntilNavCompleted(cv); var title = await Wait.Until(() => Task.FromResult(cv.DocumentTitle ?? ""), t => t.StartsWith("PERM=", StringComparison.Ordinal), 10000); window.Close(); return title; }); try { Directory.Delete(udf, true); } catch { } Assert.Equal("PERM=granted", result); } [Fact] public async Task 블랙리스트_스크립트가_차단되고_카운터에_집계된다_T3_14() { using var sta = StaDispatcher.Start(); var udf = Path.Combine(Path.GetTempPath(), $"vd_e2e_{Guid.NewGuid():N}"); var result = await sta.InvokeAsync(async () => { var window = new Window(); var browser = new Microsoft.Web.WebView2.Wpf.WebView2(); window.Content = browser; window.Show(); var env = await CoreWebView2Environment.CreateAsync(userDataFolder: udf); await browser.EnsureCoreWebView2Async(env); var cv = browser.CoreWebView2; var bl = new TrackerBlocklist(new[] { "blocked.test" }); var counter = new TrackerCounter(); cv.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); cv.WebResourceRequested += (_, e) => { if (!bl.Matches(new Uri(e.Request.Uri))) return; e.Response = env.CreateWebResourceResponse(null, 204, "Blocked", ""); counter.Record(e.Request.Uri); }; var html = "

tracker-e2e

" + "" + "" + "" + ""; cv.NavigateToString(html); await WaitUntilNavCompleted(cv); await Wait.Until(() => Task.FromResult(counter.Total), t => t >= 2, 10000); window.Close(); return counter.Total; }); try { Directory.Delete(udf, true); } catch { } Assert.Equal(2, result); // 정상 도메인(ok.example.com)은 집계되지 않음 } private static async Task WaitUntilNavCompleted(CoreWebView2 cv) { var tcs = new TaskCompletionSource(); EventHandler h = (_, _) => tcs.TrySetResult(); cv.NavigationCompleted += h; if (tcs.Task.IsCompleted) return; await Task.WhenAny(tcs.Task, Task.Delay(15000)); cv.NavigationCompleted -= h; } }