T3-11/14/15 + M2 잔여: 권한 정책 UI(PermissionGate/PermissionRequested 다이얼로그+SavesInProfile), 추적 방지(TrackerBlocklist/WebResourceRequested 차단+실드 배지·팝업), 주소창 진행 바+파라미터 제거 복사(UrlCleaner/NavProgressTracker), U5 완료 알림(Mobile.Core 감지기+Plugin.LocalNotification), 재생중 ongoing 미디어 알림, 썸네일/해상도 API(Thumbnailer 사이드카 캐시+/thumb)+라이브러리 그리드, 외부망 안내(ConnectionTroubleshoot). 147 tests GREEN
This commit is contained in:
parent
27c91c04dd
commit
46c6c321f2
26 changed files with 1058 additions and 30 deletions
46
VideoDownloader.Tests/Browser/PermissionGateTests.cs
Normal file
46
VideoDownloader.Tests/Browser/PermissionGateTests.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using VideoDownloader.Browser;
|
||||
|
||||
namespace VideoDownloader.Tests.Browser;
|
||||
|
||||
/// <summary>T3-11: 사이트별 권한 정책 — 기억된 상태 우선, 미기억은 Ask(프롬프트).</summary>
|
||||
public class PermissionGateTests
|
||||
{
|
||||
[Fact]
|
||||
public void 미기억_권한은_Ask()
|
||||
=> Assert.Equal(PermissionVerdict.Ask, new PermissionGate().Decide("notifications", "https://x.com"));
|
||||
|
||||
[Theory]
|
||||
[InlineData("allow")]
|
||||
[InlineData("deny")]
|
||||
public void 기억된_판정은_그대로_반환(string v)
|
||||
{
|
||||
var g = new PermissionGate();
|
||||
g.Remember("notifications", "https://x.com", v == "allow" ? PermissionVerdict.Allow : PermissionVerdict.Deny);
|
||||
Assert.Equal(v == "allow" ? PermissionVerdict.Allow : PermissionVerdict.Deny,
|
||||
g.Decide("notifications", "https://x.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 판정은_출처_단위_격리()
|
||||
{
|
||||
var g = new PermissionGate();
|
||||
g.Remember("geolocation", "https://a.com", PermissionVerdict.Allow);
|
||||
Assert.Equal(PermissionVerdict.Ask, g.Decide("geolocation", "https://b.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 판정은_종류_단위_격리()
|
||||
{
|
||||
var g = new PermissionGate();
|
||||
g.Remember("notifications", "https://a.com", PermissionVerdict.Allow);
|
||||
Assert.Equal(PermissionVerdict.Ask, g.Decide("camera", "https://a.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OtherSensors_등_민감_종류는_자동_거절()
|
||||
=> Assert.Equal(PermissionVerdict.Deny, new PermissionGate().Decide("OtherSensors", "https://x.com"));
|
||||
|
||||
[Fact]
|
||||
public void MultipleAutomaticDownloads_파일_다운로드는_자동_허용()
|
||||
=> Assert.Equal(PermissionVerdict.Allow, new PermissionGate().Decide("MultipleAutomaticDownloads", "https://x.com"));
|
||||
}
|
||||
40
VideoDownloader.Tests/Browser/TrackerBlockTests.cs
Normal file
40
VideoDownloader.Tests/Browser/TrackerBlockTests.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using VideoDownloader.Browser;
|
||||
|
||||
namespace VideoDownloader.Tests.Browser;
|
||||
|
||||
/// <summary>T3-14: 추적 방지 — 블랙리스트 도메인 매칭(서브도메인 포함) + 차단 카운터.</summary>
|
||||
public class TrackerBlockTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("https://google-analytics.com/analytics.js", true)] // 정확히 도메인
|
||||
[InlineData("https://www.google-analytics.com/g/collect", true)] // 서브도메인
|
||||
[InlineData("https://x.com/notgoogle-analytics.com/a", false)] // 접미사 함정 제외
|
||||
[InlineData("https://doubleclick.net/instream/ad_status.js", true)]
|
||||
[InlineData("https://safe.example.com/lib.js", false)] // 무관 도메인
|
||||
public void Matches_블랙리스트_도메인_판정(string url, bool expected)
|
||||
=> Assert.Equal(expected, new TrackerBlocklist().Matches(new Uri(url)));
|
||||
|
||||
[Fact]
|
||||
public void 커스텀_도메인_주입_가능()
|
||||
{
|
||||
var bl = new TrackerBlocklist(new[] { "blocked.test" });
|
||||
Assert.True(bl.Matches(new Uri("https://t.blocked.test/x.js")));
|
||||
Assert.False(bl.Matches(new Uri("https://ok.com/x.js")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Counter_차단_누적_도메인별_집계_변경_통지()
|
||||
{
|
||||
var c = new TrackerCounter();
|
||||
var changed = 0;
|
||||
c.Changed += () => changed++;
|
||||
Assert.Equal(0, c.Total);
|
||||
c.Record("https://www.google-analytics.com/a.js");
|
||||
c.Record("https://google-analytics.com/b.js");
|
||||
c.Record("https://scorecardresearch.com/c.js");
|
||||
Assert.Equal(3, c.Total);
|
||||
Assert.Equal(2, c.ByDomain["google-analytics.com"]);
|
||||
Assert.Equal(1, c.ByDomain["scorecardresearch.com"]);
|
||||
Assert.Equal(3, changed);
|
||||
}
|
||||
}
|
||||
80
VideoDownloader.Tests/Browser/UrlCleanerTests.cs
Normal file
80
VideoDownloader.Tests/Browser/UrlCleanerTests.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
using VideoDownloader.Browser;
|
||||
|
||||
namespace VideoDownloader.Tests.Browser;
|
||||
|
||||
/// <summary>T3-15: 파라미터 제거 복사 + 주소창 진행 상태.</summary>
|
||||
public class UrlCleanerTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("https://x.com/a?utm_source=1&b=2", "https://x.com/a")] // 쿼리 제거
|
||||
[InlineData("https://x.com/a#frag", "https://x.com/a")] // 프래그먼트 제거
|
||||
[InlineData("https://x.com/a?b=1#f", "https://x.com/a")] // 둘 다
|
||||
[InlineData("https://x.com/a/", "https://x.com/a/")] // 없으면 무변경
|
||||
[InlineData("https://x.com/a?q=한글", "https://x.com/a")] // 유니코드 쿼리
|
||||
[InlineData("x.com/a?b=1", "x.com/a")] // 스킴 없어도 동작
|
||||
public void StripQuery_파라미터와_프래그먼트를_제거(string input, string expected)
|
||||
=> Assert.Equal(expected, UrlCleaner.StripQuery(input));
|
||||
|
||||
[Fact]
|
||||
public void StripQuery_빈값_null_안전()
|
||||
{
|
||||
Assert.Equal("", UrlCleaner.StripQuery(""));
|
||||
Assert.Null(UrlCleaner.StripQuery(null));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>T3-15: 탭 전체 네비게이션 진행 집계 — 주소창 진행 바 표시 상태.</summary>
|
||||
public class NavProgressTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
public void 초기값은_비진행()
|
||||
=> Assert.False(new NavProgressTracker().IsNavigating);
|
||||
|
||||
[Fact]
|
||||
public void 시작하면_진행()
|
||||
{
|
||||
var t = new NavProgressTracker();
|
||||
t.Started();
|
||||
Assert.True(t.IsNavigating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 완료되면_비진행()
|
||||
{
|
||||
var t = new NavProgressTracker();
|
||||
t.Started();
|
||||
t.Completed();
|
||||
Assert.False(t.IsNavigating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 동시_내비게이션은_마지막_완료까지_진행() // 프레임 리다이렉트·중복 시작
|
||||
{
|
||||
var t = new NavProgressTracker();
|
||||
t.Started(); t.Started();
|
||||
t.Completed();
|
||||
Assert.True(t.IsNavigating);
|
||||
t.Completed();
|
||||
Assert.False(t.IsNavigating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void 과잉_완료로_음수_되지_않음()
|
||||
{
|
||||
var t = new NavProgressTracker();
|
||||
t.Completed();
|
||||
Assert.False(t.IsNavigating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNavigating_변경_시_INPC_발화()
|
||||
{
|
||||
var t = new NavProgressTracker();
|
||||
var fired = new List<string?>();
|
||||
t.PropertyChanged += (_, e) => fired.Add(e.PropertyName);
|
||||
t.Started();
|
||||
t.Completed();
|
||||
t.Completed(); // false→false → 이벤트 없음
|
||||
Assert.Equal(new[] { nameof(NavProgressTracker.IsNavigating), nameof(NavProgressTracker.IsNavigating) }, fired);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue