using System.Net; using System.Net.Http; using VideoDownloader.Core.Update; namespace VideoDownloader.Tests.Platform; /// /// DEPLOYMENT_PLAN §6 자동 업데이트 v1 — GitHub Releases latest 태그 비교 + 아키텍처별 설치 자산 선택. /// 네트워크는 가짜 핸들러로 격리. /// public class UpdateCheckerTests { private sealed class FakeHandler : HttpMessageHandler { private readonly HttpStatusCode _status; private readonly string _json; public HttpRequestMessage? LastRequest { get; private set; } public FakeHandler(HttpStatusCode status, string json) { _status = status; _json = json; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken ct) { LastRequest = request; return Task.FromResult(new HttpResponseMessage(_status) { Content = new StringContent(_json, System.Text.Encoding.UTF8, "application/json"), }); } } private const string ReleaseJson = """ { "tag_name": "v1.2.0", "html_url": "https://github.com/user/videodownloader/releases/tag/v1.2.0", "assets": [ { "name": "VideoDownloader-Setup-x64.exe", "browser_download_url": "https://github.com/user/videodownloader/releases/download/v1.2.0/VideoDownloader-Setup-x64.exe" }, { "name": "VideoDownloader-Setup-arm64.exe", "browser_download_url": "https://github.com/user/videodownloader/releases/download/v1.2.0/VideoDownloader-Setup-arm64.exe" } ] } """; [Theory] [InlineData("1.0.0", "v1.2.0", true)] // 최신이 더 높음 [InlineData("1.2.0", "v1.2.0", false)] // 동일 [InlineData("2.0.0", "v1.9.9", false)] // 현재가 더 높음 [InlineData("1.0.0.0", "v1.0.1", true)] // 어셈블리 4자리 버전 허용 [InlineData("1.0.0", "garbage", false)] // 파싱 불가면 갱신 아님 public void 버전비교(string current, string latest, bool expected) => Assert.Equal(expected, UpdateChecker.IsNewer(current, latest)); [Fact] public void 자산선택_x64와_arm64_구분() { var assets = new[] { new UpdateAsset("VideoDownloader-Setup-x64.exe", "https://dl/x64"), new UpdateAsset("VideoDownloader-Setup-arm64.exe", "https://dl/arm64"), }; Assert.Equal("https://dl/x64", UpdateChecker.SelectAsset(assets, "win-x64")!.DownloadUrl); Assert.Equal("https://dl/arm64", UpdateChecker.SelectAsset(assets, "win-arm64")!.DownloadUrl); Assert.Null(UpdateChecker.SelectAsset(assets, "linux-x64")); // 일치 없음 } [Fact] public async Task 최신확인_새버전이면_자산과_릴리스URL() { var handler = new FakeHandler(HttpStatusCode.OK, ReleaseJson); var checker = new UpdateChecker(handler); var result = await checker.CheckAsync("user/videodownloader", "1.0.0", "win-x64"); Assert.NotNull(result); Assert.True(result!.IsNewer); Assert.Equal("1.2.0", result.LatestVersion); Assert.Equal("https://github.com/user/videodownloader/releases/tag/v1.2.0", result.ReleaseUrl); Assert.Equal("VideoDownloader-Setup-x64.exe", result.Asset!.Name); // 기본 채널(자체 Forgejo) 엔드포인트 + User-Agent(GitHub 채널 호환 규약 유지) Assert.Equal("https://git.chanpaca.net/api/v1/repos/user/videodownloader/releases/latest", handler.LastRequest!.RequestUri!.ToString()); Assert.NotEmpty(handler.LastRequest.Headers.UserAgent); } [Fact] public async Task 최신확인_이미_최신이면_IsNewer_false() { var checker = new UpdateChecker(new FakeHandler(HttpStatusCode.OK, ReleaseJson)); var result = await checker.CheckAsync("user/videodownloader", "1.2.0", "win-x64"); Assert.False(result!.IsNewer); } [Fact] public async Task 최신확인_API_실패는_예외로_드러남() { var checker = new UpdateChecker(new FakeHandler(HttpStatusCode.NotFound, """{"message":"Not Found"}""")); await Assert.ThrowsAsync(() => checker.CheckAsync("user/none", "1.0.0", "win-x64")); } // ── 배포 채널 전환 (DEPLOYMENT_PLAN §5-bis/§5-ter): 기본 = 자체 Forgejo 공개 릴리스 저장소 ── [Fact] public async Task 최신확인_기본은_Forgejo_공개저장소_API() { var handler = new FakeHandler(HttpStatusCode.OK, ReleaseJson); var checker = new UpdateChecker(handler); await checker.CheckAsync("Video-Downloader/releases", "1.0.0", "win-x64"); // Forgejo 는 /api/v1 접두가 필요 — GitHub 형태(api.github.com/repos/...)와 경로 규칙만 다르다 Assert.Equal( "https://git.chanpaca.net/api/v1/repos/Video-Downloader/releases/releases/latest", handler.LastRequest!.RequestUri!.ToString()); } [Fact] public async Task 최신확인_apiBase_지정으로_GitHub_채널도_호환() { var handler = new FakeHandler(HttpStatusCode.OK, ReleaseJson); var checker = new UpdateChecker(handler); var result = await checker.CheckAsync("user/videodownloader", "1.0.0", "win-x64", apiBase: "https://api.github.com"); Assert.True(result!.IsNewer); Assert.Equal("https://api.github.com/repos/user/videodownloader/releases/latest", handler.LastRequest!.RequestUri!.ToString()); } [Fact] public void 자산선택_osx는_tar_gz_도_허용() { // macOS 프리뷰는 dmg 가 아니라 tar.gz 로 배포 중 — dmg 만 허용하면 mac 은 영원히 자산 없음 var assets = new[] { new UpdateAsset("mediabrowser-osx-arm64.tar.gz", "https://dl/osx-arm64"), new UpdateAsset("mediabrowser-osx-x64.tar.gz", "https://dl/osx-x64"), }; Assert.Equal("https://dl/osx-arm64", UpdateChecker.SelectAsset(assets, "osx-arm64")!.DownloadUrl); Assert.Equal("https://dl/osx-x64", UpdateChecker.SelectAsset(assets, "osx-x64")!.DownloadUrl); } [Fact] public void 업데이트_기본채널은_공개릴리스저장소() { var config = new VideoDownloader.Core.Config.AppConfig(); Assert.Equal("Video-Downloader/releases", config.UpdateRepo); Assert.Equal("https://git.chanpaca.net/api/v1", config.UpdateApiBase); } }