115 lines
5.8 KiB
C#
115 lines
5.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Mobile;
|
|
|
|
/// <summary>
|
|
/// 모바일 안드로이드 최신 버전 지원(minSdk 28+, targetSdk 36) 및
|
|
/// Obsolete API(DisplayAlert, Device.StartTimer, Frame 등) 퇴출 기계적 감사 게이트
|
|
/// </summary>
|
|
public class MobileAndroidModernizationTests
|
|
{
|
|
private static string MobileDir => Path.Combine(SourceTree.Root, "src", "Paca.Mobile");
|
|
private static string AndroidManifestPath => Path.Combine(MobileDir, "Platforms", "Android", "AndroidManifest.xml");
|
|
private static string MobileCsprojPath => Path.Combine(MobileDir, "Paca.Mobile.csproj");
|
|
|
|
[Fact]
|
|
public void Gate_Android_MinSdkVersion_Is_At_Least_28_And_Target_Is_36()
|
|
{
|
|
// 1. AndroidManifest.xml 검증
|
|
Assert.True(File.Exists(AndroidManifestPath), "AndroidManifest.xml 이 존재해야 합니다.");
|
|
var manifestDoc = XDocument.Load(AndroidManifestPath);
|
|
var usesSdk = manifestDoc.Descendants("uses-sdk").FirstOrDefault();
|
|
Assert.NotNull(usesSdk);
|
|
|
|
XNamespace androidNs = "http://schemas.android.com/apk/res/android";
|
|
var minSdk = (int?)usesSdk.Attribute(androidNs + "minSdkVersion");
|
|
var targetSdk = (int?)usesSdk.Attribute(androidNs + "targetSdkVersion");
|
|
|
|
Assert.NotNull(minSdk);
|
|
Assert.True(minSdk.Value >= 28, $"minSdkVersion은 최소 28(Android 9.0) 이상이어야 합니다. 현재: {minSdk.Value}");
|
|
Assert.NotNull(targetSdk);
|
|
Assert.True(targetSdk.Value >= 36, $"targetSdkVersion은 최신 36(Android 16) 이상이어야 합니다. 현재: {targetSdk.Value}");
|
|
|
|
// 2. Paca.Mobile.csproj의 SupportedOSPlatformVersion 검증
|
|
Assert.True(File.Exists(MobileCsprojPath), "Paca.Mobile.csproj 가 존재해야 합니다.");
|
|
var csprojContent = File.ReadAllText(MobileCsprojPath);
|
|
Assert.DoesNotContain("<SupportedOSPlatformVersion Condition=\"$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'\">24.0</SupportedOSPlatformVersion>", csprojContent);
|
|
Assert.Contains("<SupportedOSPlatformVersion Condition=\"$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'\">28.0</SupportedOSPlatformVersion>", csprojContent);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate_Mobile_Packages_Are_Modernized()
|
|
{
|
|
var csprojContent = File.ReadAllText(MobileCsprojPath);
|
|
|
|
// 1. ZXing.Net.Maui.Controls 최신화 및 넷6 구형 패키지(0.4.0) 퇴출
|
|
Assert.DoesNotContain("Include=\"ZXing.Net.Maui.Controls\" Version=\"0.4.0\"", csprojContent);
|
|
Assert.Contains("Include=\"ZXing.Net.Maui.Controls\" Version=\"0.10.4\"", csprojContent);
|
|
|
|
// 2. CommunityToolkit.Maui.MediaElement 최신화(.NET 10 대응 10.0.0) 및 4.0.0 퇴출
|
|
Assert.DoesNotContain("Include=\"CommunityToolkit.Maui.MediaElement\" Version=\"4.0.0\"", csprojContent);
|
|
Assert.Contains("Include=\"CommunityToolkit.Maui.MediaElement\" Version=\"10.0.0\"", csprojContent);
|
|
|
|
// 3. Plugin.LocalNotification 최신화
|
|
Assert.DoesNotContain("Include=\"Plugin.LocalNotification\" Version=\"11.1.0\"", csprojContent);
|
|
Assert.Contains("Include=\"Plugin.LocalNotification\" Version=\"14.1.1\"", csprojContent);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate_Mobile_No_Obsolete_Maui_Apis_Audit()
|
|
{
|
|
var csFiles = Directory.GetFiles(MobileDir, "*.cs", SearchOption.AllDirectories)
|
|
.Where(f => !f.Contains("\\obj\\") && !f.Contains("/obj/") && !f.Contains("\\bin\\") && !f.Contains("/bin/"))
|
|
.ToList();
|
|
|
|
foreach (var file in csFiles)
|
|
{
|
|
var fileName = Path.GetFileName(file);
|
|
var lines = File.ReadAllLines(file);
|
|
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
var line = lines[i];
|
|
var trimmed = line.Trim();
|
|
|
|
// 주석 라인은 제외
|
|
if (trimmed.StartsWith("//") || trimmed.StartsWith("/*") || trimmed.StartsWith("*"))
|
|
continue;
|
|
|
|
// 1. DisplayAlert( -> DisplayAlertAsync 사용 필수
|
|
if (line.Contains("DisplayAlert(") && !line.Contains("DisplayAlertAsync"))
|
|
{
|
|
Assert.Fail($"[{fileName}:{i + 1}] Obsolete API 'DisplayAlert' 가 발견되었습니다. 'DisplayAlertAsync'를 사용하세요: {trimmed}");
|
|
}
|
|
|
|
// 2. DisplayActionSheet( -> DisplayActionSheetAsync 사용 필수
|
|
if (line.Contains("DisplayActionSheet(") && !line.Contains("DisplayActionSheetAsync"))
|
|
{
|
|
Assert.Fail($"[{fileName}:{i + 1}] Obsolete API 'DisplayActionSheet' 가 발견되었습니다. 'DisplayActionSheetAsync'를 사용하세요: {trimmed}");
|
|
}
|
|
|
|
// 3. Device.StartTimer 퇴출
|
|
if (line.Contains("Device.StartTimer"))
|
|
{
|
|
Assert.Fail($"[{fileName}:{i + 1}] Obsolete API 'Device.StartTimer' 가 발견되었습니다. 'Dispatcher.StartTimer'를 사용하세요: {trimmed}");
|
|
}
|
|
|
|
// 4. new Frame 퇴출 (Android FrameLayout 레이아웃 제외 MAUI Frame 컨트롤)
|
|
if (line.Contains("new Frame") && !line.Contains("FrameLayout"))
|
|
{
|
|
Assert.Fail($"[{fileName}:{i + 1}] Obsolete API 'Frame' 이 발견되었습니다. 'Border'를 사용하세요: {trimmed}");
|
|
}
|
|
|
|
// 5. MainPage 직접 탐색 퇴출 (Application.Current.MainPage)
|
|
if (line.Contains(".MainPage"))
|
|
{
|
|
Assert.Fail($"[{fileName}:{i + 1}] Deprecated API 'MainPage' 가 발견되었습니다. Windows[0].Page 를 사용하세요: {trimmed}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|