using VideoDownloader.Mobile; namespace VideoDownloader.Tests.Mobile; /// Mobile.Core 순수 로직 — U5 완료 감지, 외부망 안내, U4 저장소(파일 시스템). 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); } }