releases/VideoDownloader.Tests/E2E/PermissionTrackerE2ETests.cs

109 lines
4.6 KiB
C#

using System.Windows;
using System.Windows.Threading;
using Microsoft.Web.WebView2.Core;
using VideoDownloader.Browser;
using VideoDownloader.Tests.Infrastructure;
namespace VideoDownloader.Tests.E2E;
/// <summary>
/// E2E (T3-11/T3-14): 실제 WebView2 에서 권한 정책 응답과 추적 요청 차단·집계를 검증.
/// TabViewService 와 동일한 배선(PermissionGate/TrackerBlocklist/TrackerCounter)을 사용한다.
/// </summary>
[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 = "<html><body><script>Notification.requestPermission().then(p => { document.title = 'PERM=' + p; });</script></body></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 = "<html><body><p>tracker-e2e</p>" +
"<script src=\"https://www.blocked.test/a.js\"></script>" +
"<script src=\"https://www.blocked.test/b.js\"></script>" +
"<script src=\"https://ok.example.com/c.js\"></script>" +
"</body></html>";
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<CoreWebView2NavigationCompletedEventArgs> h = (_, _) => tcs.TrySetResult();
cv.NavigationCompleted += h;
if (tcs.Task.IsCompleted) return;
await Task.WhenAny(tcs.Task, Task.Delay(15000));
cv.NavigationCompleted -= h;
}
}