using System; using System.IO; using System.Threading.Tasks; using Paca.Mobile.Core; using Paca.Tests.Infrastructure; using Xunit; namespace Paca.Tests.Mobile; /// /// 모바일 AdMob 설정, 워터폴 폴백 및 보상형 광고 라이프사이클 TDD 게이트 테스트 (Gate 194 ~ Gate 196) /// public class MobileAdMobWaterfallAndRewardGateTests { private static string MobileCoreDir => Directory.Exists(Path.Combine(SourceTree.Root, "src", "Paca.Mobile.Core")) ? Path.Combine(SourceTree.Root, "src", "Paca.Mobile.Core") : Path.Combine(SourceTree.Root, "Paca.Mobile.Core"); private static string MobileProjectDir => Directory.Exists(Path.Combine(SourceTree.Root, "src", "Paca.Mobile")) ? Path.Combine(SourceTree.Root, "src", "Paca.Mobile") : Path.Combine(SourceTree.Root, "Paca.Mobile"); [Fact] public void Gate194_AdMobConfig_GoogleOfficialTestIds_And_Validation_Audit() { // 1. Google 공식 Android AdMob Test IDs 기본값 검증 var config = AdMobConfig.DefaultTestConfig; Assert.Equal("ca-app-pub-3940256099942544~3347511713", config.AppId); Assert.Equal("ca-app-pub-3940256099942544/6300978111", config.BannerAdUnitId); Assert.Equal("ca-app-pub-3940256099942544/1033173712", config.InterstitialAdUnitId); Assert.Equal("ca-app-pub-3940256099942544/5224354917", config.RewardedAdUnitId); Assert.True(config.IsTestMode); // 2. 커스텀 프로덕션 ID 주입 및 유효성 검증 var prodConfig = new AdMobConfig( appId: "ca-app-pub-1234567890123456~1234567890", bannerAdUnitId: "ca-app-pub-1234567890123456/1111111111", interstitialAdUnitId: "ca-app-pub-1234567890123456/2222222222", rewardedAdUnitId: "ca-app-pub-1234567890123456/3333333333", isTestMode: false ); Assert.False(prodConfig.IsTestMode); Assert.True(prodConfig.IsValid()); // 3. 잘못된 형식(유효하지 않은 접두사) 검증 방어 var invalidConfig = new AdMobConfig( appId: "invalid-app-id", bannerAdUnitId: "", interstitialAdUnitId: "", rewardedAdUnitId: "", isTestMode: false ); Assert.False(invalidConfig.IsValid()); } [Fact] public async Task Gate195_AdMobWaterfall_SuccessAndFallbackLifecycle_Audit() { var localAdManager = new MobileAdManager(); var fallbackAd = localAdManager.GetAdForPlacement(MobileAdPlacement.NewTabPageSponsoredCard); Assert.NotNull(fallbackAd); // 1. 성공 시나리오 Mock 어댑터 var successAdapter = new MockAdMobAdapter(shouldSucceed: true, adUnitId: "test-banner-unit"); var controller = new AdMobWaterfallController(successAdapter, localAdManager); var result = await controller.RequestBannerAdAsync(MobileAdPlacement.NewTabPageSponsoredCard); Assert.NotNull(result); Assert.Equal(AdSourceType.GoogleAdMob, result.SourceType); Assert.Equal("test-banner-unit", result.AdMobUnitId); Assert.True(result.IsAdMobActive); // 인상 추적 확인 controller.TrackImpression(result); Assert.Equal(1, successAdapter.ImpressionCount); // 2. 실패 시나리오 (No Fill / Offline) -> 자체 스폰서 배너로 자동 Waterfall Fallback var failureAdapter = new MockAdMobAdapter(shouldSucceed: false, adUnitId: "failing-unit"); var fallbackController = new AdMobWaterfallController(failureAdapter, localAdManager); var fallbackResult = await fallbackController.RequestBannerAdAsync(MobileAdPlacement.NewTabPageSponsoredCard); Assert.NotNull(fallbackResult); Assert.Equal(AdSourceType.DirectSponsorFallback, fallbackResult.SourceType); Assert.False(fallbackResult.IsAdMobActive); Assert.NotNull(fallbackResult.FallbackDirectAd); Assert.Equal(fallbackAd.Id, fallbackResult.FallbackDirectAd.Id); // Fallback 인상 추적 확인 fallbackController.TrackImpression(fallbackResult); Assert.Equal(0, failureAdapter.ImpressionCount); Assert.True(localAdManager.GetImpressionCount(fallbackAd.Id) > 0); } [Fact] public async Task Gate196_AdMobRewardedAd_BoostToken_Lifecycle_Audit() { var rewardManager = new RewardedBoostTokenManager(); Assert.False(rewardManager.IsBoostActive); var mockAdapter = new MockAdMobAdapter(shouldSucceed: true, adUnitId: "rewarded-unit"); // 1. 보상형 광고 시청 완료 콜백 시뮬레이션 bool rewardTriggered = false; mockAdapter.OnUserEarnedReward += (type, amount) => { rewardTriggered = true; rewardManager.GrantBoost(TimeSpan.FromHours(1)); }; var showResult = await mockAdapter.ShowRewardedAdAsync(); Assert.True(showResult); Assert.True(rewardTriggered); // 2. 부스트 토큰 활성화 확인 Assert.True(rewardManager.IsBoostActive); Assert.True(rewardManager.RemainingBoostTime.TotalMinutes > 50); // 3. 만료 시뮬레이션 rewardManager.RevokeBoost(); Assert.False(rewardManager.IsBoostActive); } [Fact] public void Gate197_AdMobConfig_PacaProduction_RealIds_And_ResolveByBuild_Audit() { // 1. PACA 프로덕션 설정 실제 ID 검증 var prod = AdMobConfig.PacaProduction; Assert.Equal("ca-app-pub-1039714767792854~5680462775", prod.AppId); Assert.Equal("ca-app-pub-1039714767792854/3110491400", prod.BannerAdUnitId); Assert.False(prod.IsTestMode); Assert.True(prod.IsValid()); // 2. 환경변수 미설정 시 빌드 구성에 따른 해석 (ADMOB_APP_ID / ADMOB_BANNER_ID 백업 후 제거) var backupAppId = Environment.GetEnvironmentVariable("ADMOB_APP_ID"); var backupBannerId = Environment.GetEnvironmentVariable("ADMOB_BANNER_ID"); try { Environment.SetEnvironmentVariable("ADMOB_APP_ID", null); Environment.SetEnvironmentVariable("ADMOB_BANNER_ID", null); var debugResolved = AdMobConfig.Resolve(isDebugBuild: true); Assert.Equal(AdMobConfig.DefaultTestConfig.AppId, debugResolved.AppId); Assert.Equal(AdMobConfig.DefaultTestConfig.BannerAdUnitId, debugResolved.BannerAdUnitId); Assert.True(debugResolved.IsTestMode); var releaseResolved = AdMobConfig.Resolve(isDebugBuild: false); Assert.Equal(AdMobConfig.PacaProduction.AppId, releaseResolved.AppId); Assert.Equal(AdMobConfig.PacaProduction.BannerAdUnitId, releaseResolved.BannerAdUnitId); Assert.False(releaseResolved.IsTestMode); // 3. 환경변수가 설정되어 있으면 빌드 구성과 무관하게 환경변수 값을 우선한다 Environment.SetEnvironmentVariable("ADMOB_APP_ID", "ca-app-pub-9999999999999999~1111111111"); Environment.SetEnvironmentVariable("ADMOB_BANNER_ID", "ca-app-pub-9999999999999999/2222222222"); var envDebugResolved = AdMobConfig.Resolve(isDebugBuild: true); Assert.Equal("ca-app-pub-9999999999999999~1111111111", envDebugResolved.AppId); Assert.Equal("ca-app-pub-9999999999999999/2222222222", envDebugResolved.BannerAdUnitId); var envReleaseResolved = AdMobConfig.Resolve(isDebugBuild: false); Assert.Equal("ca-app-pub-9999999999999999~1111111111", envReleaseResolved.AppId); Assert.Equal("ca-app-pub-9999999999999999/2222222222", envReleaseResolved.BannerAdUnitId); } finally { Environment.SetEnvironmentVariable("ADMOB_APP_ID", backupAppId); Environment.SetEnvironmentVariable("ADMOB_BANNER_ID", backupBannerId); } } [Fact] public void Gate198_AndroidManifest_Uses_PacaProduction_AdMobAppId_Audit() { var manifestPath = Path.Combine(MobileProjectDir, "Platforms", "Android", "AndroidManifest.xml"); var manifestXml = File.ReadAllText(manifestPath); Assert.Contains( $"android:value=\"{AdMobConfig.PacaProduction.AppId}\"", manifestXml); Assert.DoesNotContain("ca-app-pub-3940256099942544~3347511713", manifestXml); } [Fact] public void Gate199_PacaMobile_Csproj_References_GooglePlayServicesAds_Audit() { var csprojPath = Path.Combine(MobileProjectDir, "Paca.Mobile.csproj"); var csprojXml = File.ReadAllText(csprojPath); Assert.Contains("Xamarin.GooglePlayServices.Ads", csprojXml); } [Fact] public void Gate200_AdMobConfig_ForceRealTestAds_WhenUnderReview_Audit() { // 1. 기본 테스트 설정 및 프로덕션 설정에서 EffectiveBannerAdUnitId 검증 var testConfig = AdMobConfig.DefaultTestConfig; Assert.Equal("ca-app-pub-3940256099942544/6300978111", testConfig.EffectiveBannerAdUnitId); Assert.True(testConfig.IsTestMode); // 2. 프로덕션 설정이라도 forceTestAds 가 활성화되면 실제 Google 공식 테스트 배너 ID 반환 var prodUnderReview = AdMobConfig.PacaProduction with { ForceTestAds = true }; Assert.True(prodUnderReview.ForceTestAds); Assert.Equal("ca-app-pub-3940256099942544/6300978111", prodUnderReview.EffectiveBannerAdUnitId); // 3. Resolve 메서드에서 검토 필요 상태를 위한 forceTestAds 기본 동작 검증 var backupForce = Environment.GetEnvironmentVariable("ADMOB_FORCE_TEST_ADS"); try { Environment.SetEnvironmentVariable("ADMOB_FORCE_TEST_ADS", "true"); var resolvedWithForce = AdMobConfig.Resolve(isDebugBuild: false); Assert.Equal("ca-app-pub-3940256099942544/6300978111", resolvedWithForce.EffectiveBannerAdUnitId); Assert.True(resolvedWithForce.ForceTestAds); } finally { Environment.SetEnvironmentVariable("ADMOB_FORCE_TEST_ADS", backupForce); } } [Fact] public void Gate201_AndroidAdMobAdapter_RequestConfiguration_And_TestDevice_Audit() { var adapterPath = Path.Combine(MobileProjectDir, "Platforms", "Android", "AndroidAdMobAdapter.cs"); var adapterCode = File.ReadAllText(adapterPath); // Google Mobile Ads SDK 의 RequestConfiguration 설정 및 테스트 디바이스 등록 보증 Assert.Contains("RequestConfiguration", adapterCode); Assert.Contains("SetTestDeviceIds", adapterCode); Assert.Contains("DeviceIdEmulator", adapterCode); Assert.Contains("EffectiveBannerAdUnitId", adapterCode); } }