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:
Yun Chan 2026-08-15 18:11:49 +09:00
parent 27c91c04dd
commit 46c6c321f2
26 changed files with 1058 additions and 30 deletions

View file

@ -0,0 +1,106 @@
using VideoDownloader.Mobile;
namespace VideoDownloader.Tests.Mobile;
/// <summary>Mobile.Core 순수 로직 — U5 완료 감지, 외부망 안내, U4 저장소(파일 시스템).</summary>
public class MobileCoreTests
{
// ── U5 완료 알림 감지 ──
[Fact]
public void 완료_전환_항목_ID_반환()
{
var d = new QueueCompletionDetector();
var prev = new[] { ("a", "다운로드 중"), ("b", "대기") };
var next = new[] { ("a", "완료"), ("b", "다운로드 중") };
Assert.Equal(new[] { "a" }, d.Detect(prev, next));
}
[Fact]
public void 이미_완료였던_항목은_재알림_없음()
{
var d = new QueueCompletionDetector();
var prev = new[] { ("a", "완료") };
var next = new[] { ("a", "완료") };
Assert.Empty(d.Detect(prev, next));
}
[Fact]
public void 첫_폴링에서_이미_완료된_항목은_무시() // 앱 시작 시 과거 완료 스팸 방지
{
var d = new QueueCompletionDetector();
Assert.Empty(d.Detect(Array.Empty<(string, string)>(), new[] { ("a", "완료") }));
}
[Fact]
public void 목록에서_사라진_항목은_알림_없음()
{
var d = new QueueCompletionDetector();
var prev = new[] { ("a", "다운로드 중") };
Assert.Empty(d.Detect(prev, Array.Empty<(string, string)>()));
}
// ── 외부망 안내(MOBILE_PLAN §7) ──
[Fact]
public void 미연결_안내()
{
var m = ConnectionTroubleshoot.Message(isConfigured: false, lastError: null);
Assert.Contains("연결", m);
}
[Fact]
public void 연결됐지만_실패_시_같은네트워크_VPN_안내()
{
var m = ConnectionTroubleshoot.Message(isConfigured: true, lastError: "No such host is known.");
Assert.Contains("같은 네트워크", m);
Assert.Contains("Tailscale", m); // 계획 §3: 자체 릴레이 대신 Tailscale 문서화
}
[Fact]
public void 정상_시_빈_문자열()
=> Assert.Equal("", ConnectionTroubleshoot.Message(isConfigured: true, lastError: null));
// ── U4 저장소(임시 디렉터리로 파일 시스템 검증) ──
[Fact]
public void OfflineStore_기록_목록_삭제_라운드트립()
{
var baseDir = Path.Combine(Path.GetTempPath(), $"vd_core_{Guid.NewGuid():N}");
var store = new OfflineStore(baseDir);
Assert.Empty(store.List());
var p = store.PathFor("id1");
Directory.CreateDirectory(Path.GetDirectoryName(p)!);
File.WriteAllBytes(p, new byte[123]);
store.Record("id1", "video.mp4");
Assert.True(store.Exists("id1"));
var list = store.List();
Assert.Single(list);
Assert.Equal(("id1", "video.mp4", 123L), (list[0].Id, list[0].FileName, list[0].SizeBytes));
store.Delete("id1");
Assert.False(store.Exists("id1"));
Assert.Empty(store.List());
Directory.Delete(baseDir, true);
}
[Fact]
public void OfflineStore_재시작_시_매니페스트_유지()
{
var baseDir = Path.Combine(Path.GetTempPath(), $"vd_core_{Guid.NewGuid():N}");
var store = new OfflineStore(baseDir);
var p = store.PathFor("id2");
Directory.CreateDirectory(Path.GetDirectoryName(p)!);
File.WriteAllBytes(p, new byte[10]);
store.Record("id2", "keep.mp4");
var reopened = new OfflineStore(baseDir); // 새 인스턴스 = 앱 재시작
Assert.True(reopened.Exists("id2"));
Assert.Equal("keep.mp4", reopened.List().Single().FileName);
Directory.Delete(baseDir, true);
}
}