feat(mobile): advance Paca.Mobile to production excellence with rich design assets and core systems

- Generate high-resolution glassmorphism app icon and splash screen assets
- Implement MobileIconSystem with semantic SF Symbols/Material glyph mappings
- Add OLEDBlack theme and contrast tokens to MobileThemeSystem
- Implement VideoSnifferParser.NegotiateBestQuality for multi-stream quality resolution
- Enhance DiscoverPage, FavoritesPage, and SyncSettingsPage with production controls
- Add Gate 103-114 test suites with 100% test pass rate
This commit is contained in:
Yun Chan 2026-09-03 22:13:54 +09:00
parent dbc50abb71
commit f1949024fc
81 changed files with 8611 additions and 0 deletions

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using Paca.Mobile.Core;
using Xunit;
namespace Paca.Tests.Mobile;
/// <summary>
/// 로컬 기기 디스커버리, 원격 클립보드, 절전/데이터 가드 및 복원성 게이트 (Gate 109 ~ Gate 114)
/// </summary>
public class MobileAdvancedConnectivityGateTests
{
[Fact]
public void Gate109_PacaHostBeacon_Discovery_Audit()
{
var beacon = new PacaHostBeacon();
var beaconPayload = "PACA_BEACON|DESKTOP-PACA|192.168.1.100:52100|token123";
var host = beacon.ParseBeacon(beaconPayload);
Assert.NotNull(host);
Assert.Equal("DESKTOP-PACA", host.HostName);
Assert.Equal("192.168.1.100:52100", host.Endpoint);
Assert.Equal("token123", host.AuthToken);
}
[Fact]
public void Gate110_CrossDeviceClipboardSync_Audit()
{
var clipSync = new CrossDeviceClipboardSync();
string? receivedUrl = null;
clipSync.OnMediaUrlReceived += url => receivedUrl = url;
// PC로부터 수신된 클립보드 패킷 처리
clipSync.ProcessIncomingPayload("https://twitter.com/test/status/123456");
Assert.Equal("https://twitter.com/test/status/123456", receivedUrl);
// 비미디어 텍스트는 필터링되어 무시되어야 함
receivedUrl = null;
clipSync.ProcessIncomingPayload("Hello this is not a media url");
Assert.Null(receivedUrl);
}
[Fact]
public void Gate111_MobilePowerSaverPolicy_Audit()
{
var policy = new MobilePowerSaverPolicy();
// 기본 6 세그먼트
Assert.Equal(6, policy.GetRecommendedSegments(batteryLevelPercent: 80, isCharging: false));
// 배터리 15% 이하 저전력 모드 -> 2개로 제한
Assert.Equal(2, policy.GetRecommendedSegments(batteryLevelPercent: 12, isCharging: false));
// 충전 중일 때는 100% 터보 허용
Assert.Equal(16, policy.GetRecommendedSegments(batteryLevelPercent: 12, isCharging: true));
}
[Fact]
public void Gate112_MobileDataSaverGuard_Audit()
{
var dataGuard = new MobileDataSaverGuard();
// 100MB 기본 임계값
Assert.Equal(100_000_000L, dataGuard.CellularWarningThresholdBytes);
// Wi-Fi 상태에서는 대용량도 즉시 허용
Assert.True(dataGuard.CanDownload(fileSizeBytes: 500_000_000, isWifi: true));
// 셀룰러 상태에서 500MB는 경고 플래그
Assert.False(dataGuard.CanDownload(fileSizeBytes: 500_000_000, isWifi: false));
// 셀룰러 상태에서 50MB는 허용
Assert.True(dataGuard.CanDownload(fileSizeBytes: 50_000_000, isWifi: false));
}
[Fact]
public void Gate113_MobileThumbnailCacheManager_Audit()
{
var cache = new MobileThumbnailCacheManager();
Assert.Null(cache.Get("test-key"));
cache.Put("test-key", new byte[] { 0xFF, 0xD8, 0xFF });
var cached = cache.Get("test-key");
Assert.NotNull(cached);
Assert.Equal(3, cached.Length);
// 캐시 삭제
cache.Evict("test-key");
Assert.Null(cache.Get("test-key"));
}
[Fact]
public void Gate114_NetworkFlapping_Stress_Resilience_Audit()
{
var qm = new MobileQueueManager();
var guard = new MobileNetworkGuard(qm);
var item = qm.Enqueue("https://example.com/live.m3u8", "Live Stream");
item.Status = MobileQueueStatus.Downloading;
// 10회 연속 단절 및 재연결 플래핑 스트레스
for (int i = 0; i < 10; i++)
{
guard.OnNetworkLost();
Assert.Equal(MobileQueueStatus.PausedNetwork, item.Status);
guard.OnNetworkRestored(isWifi: (i % 2 == 0));
Assert.Equal(MobileQueueStatus.Downloading, item.Status);
}
}
}