feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization

This commit is contained in:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.IO;
using Paca.Mobile.Core;
using Xunit;
namespace Paca.Tests.Mobile;
/// <summary>
/// Gate 147 ~ 150: 모바일 오프라인 미디어 라이브러리 & 스토리지 대시보드 기계적 검증
/// </summary>
public class MobileOfflineLibraryDashboardGateTests
{
[Fact]
public void Gate147_OfflineMediaItem_DisplayFormatting()
{
var item1 = new OfflineMediaItem(
Id: "media-1",
FileName: "RickRoll_4K.mp4",
SizeBytes: 104_857_600L, // 100 MB
LocalFilePath: "/data/user/0/com.paca.mobile/files/RickRoll_4K.mp4",
DownloadedAt: DateTimeOffset.UtcNow,
IsIntegrityVerified: true
);
Assert.Equal("100.0 MB", item1.DisplaySize);
Assert.True(item1.IsIntegrityVerified);
var item2 = new OfflineMediaItem(
Id: "media-2",
FileName: "LongVideo_HDR.mkv",
SizeBytes: 1_610_612_736L, // 1.5 GB
LocalFilePath: "/data/user/0/com.paca.mobile/files/LongVideo_HDR.mkv",
DownloadedAt: DateTimeOffset.UtcNow,
IsIntegrityVerified: true
);
Assert.Equal("1.5 GB", item2.DisplaySize);
}
[Fact]
public void Gate148_LocalStorageDashboard_Calculates_Usage_Percentages()
{
// 총 128 GB (137,438,953,472 B), 남은 용량 64 GB, Paca 사용 6.4 GB (5%)
long totalStorage = 128L * 1024 * 1024 * 1024;
long freeStorage = 64L * 1024 * 1024 * 1024;
long pacaStorage = (long)(totalStorage * 0.05); // 5%
var dashboard = LocalStorageDashboard.Calculate(totalStorage, freeStorage, pacaStorage);
Assert.Equal(5.0, Math.Round(dashboard.PacaUsagePercent, 1));
Assert.Equal(50.0, Math.Round(dashboard.FreePercent, 1));
Assert.Contains("Paca", dashboard.StorageSummaryText);
Assert.Contains("여유", dashboard.StorageSummaryText);
}
[Fact]
public void Gate149_OfflineLibraryManager_Filtering_And_Sorting()
{
var manager = new OfflineLibraryManager();
manager.RegisterMedia(new OfflineMediaItem("id-1", "Alpha.mp4", 50_000_000, "/path/1", DateTimeOffset.UtcNow.AddHours(-2), true));
manager.RegisterMedia(new OfflineMediaItem("id-2", "Beta.mp3", 10_000_000, "/path/2", DateTimeOffset.UtcNow.AddHours(-1), true));
manager.RegisterMedia(new OfflineMediaItem("id-3", "Gamma.mp4", 200_000_000, "/path/3", DateTimeOffset.UtcNow, true));
// 1. 최신순 정렬 검증 (Gamma -> Beta -> Alpha)
var recentList = manager.GetItems(MediaFilter.All, MediaSortOrder.NewestFirst);
Assert.Equal(3, recentList.Count);
Assert.Equal("Gamma.mp4", recentList[0].FileName);
Assert.Equal("Beta.mp3", recentList[1].FileName);
Assert.Equal("Alpha.mp4", recentList[2].FileName);
// 2. 용량 큰 순 정렬 검증 (Gamma(200MB) -> Alpha(50MB) -> Beta(10MB))
var sizeList = manager.GetItems(MediaFilter.All, MediaSortOrder.LargestFirst);
Assert.Equal("Gamma.mp4", sizeList[0].FileName);
Assert.Equal("Alpha.mp4", sizeList[1].FileName);
Assert.Equal("Beta.mp3", sizeList[2].FileName);
// 3. 오디오만 필터링 검증
var audioList = manager.GetItems(MediaFilter.AudioOnly, MediaSortOrder.NewestFirst);
Assert.Single(audioList);
Assert.Equal("Beta.mp3", audioList[0].FileName);
// 4. 비디오만 필터링 검증
var videoList = manager.GetItems(MediaFilter.VideoOnly, MediaSortOrder.NewestFirst);
Assert.Equal(2, videoList.Count);
Assert.DoesNotContain(videoList, x => x.FileName.EndsWith(".mp3"));
}
[Fact]
public void Gate150_OfflineLibraryManager_Deletion_Updates_Quota()
{
var manager = new OfflineLibraryManager();
manager.RegisterMedia(new OfflineMediaItem("id-1", "Video1.mp4", 100_000_000, "/path/1", DateTimeOffset.UtcNow, true));
manager.RegisterMedia(new OfflineMediaItem("id-2", "Video2.mp4", 50_000_000, "/path/2", DateTimeOffset.UtcNow, true));
Assert.Equal(150_000_000L, manager.TotalPacaBytes);
// id-1 삭제
var removed = manager.RemoveMedia("id-1");
Assert.True(removed);
Assert.Equal(50_000_000L, manager.TotalPacaBytes);
Assert.Single(manager.GetItems(MediaFilter.All, MediaSortOrder.NewestFirst));
}
}