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:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
|
|
@ -0,0 +1,236 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Mobile.Core;
|
||||
using Paca.Tests.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// 모바일 AdMob 설정, 워터폴 폴백 및 보상형 광고 라이프사이클 TDD 게이트 테스트 (Gate 194 ~ Gate 196)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue