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
164
tests/Paca.Tests/E2E/ProfileAvatarPresetAndUploadGateTests.cs
Normal file
164
tests/Paca.Tests/E2E/ProfileAvatarPresetAndUploadGateTests.cs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Paca.App;
|
||||
using Paca.Browser.Profiles;
|
||||
using Paca.Tests.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.E2E;
|
||||
|
||||
/// <summary>
|
||||
/// 20종 추상 아바타 프리셋 및 사용자 커스텀 이미지 업로드 TDD 검증 게이트
|
||||
/// </summary>
|
||||
public sealed class ProfileAvatarPresetAndUploadGateTests
|
||||
{
|
||||
private static string AppAssetsAvatarDir =>
|
||||
Path.Combine(SourceTree.Root, "src", "Paca.App", "Assets", "Avatars");
|
||||
|
||||
[Fact]
|
||||
public void Gate1_PresetAvatars_20Images_ExistAndValidBitmap_Audit()
|
||||
{
|
||||
// 1. Assets/Avatars 디렉터리가 존재해야 함
|
||||
Assert.True(Directory.Exists(AppAssetsAvatarDir), $"Avatars directory missing: {AppAssetsAvatarDir}");
|
||||
|
||||
// 2. avatar_01.png ~ avatar_20.png 20종 존재 검증
|
||||
for (int i = 1; i <= 20; i++)
|
||||
{
|
||||
string fileName = $"avatar_{i:D2}.png";
|
||||
string filePath = Path.Combine(AppAssetsAvatarDir, fileName);
|
||||
Assert.True(File.Exists(filePath), $"Preset avatar missing: {fileName}");
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
Assert.True(fileInfo.Length >= 1024, $"Avatar file {fileName} is too small ({fileInfo.Length} bytes)");
|
||||
|
||||
// 유효한 이미지인지 디코딩 무결성 검증
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
Assert.True(decoder.Frames.Count > 0, $"Avatar file {fileName} has no valid frames");
|
||||
Assert.True(decoder.Frames[0].PixelWidth > 0 && decoder.Frames[0].PixelHeight > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate2_ProfileAvatarHelper_GetPresetAvatars_Returns20Items()
|
||||
{
|
||||
var presets = ProfileAvatarHelper.GetPresetAvatarPaths();
|
||||
Assert.NotNull(presets);
|
||||
Assert.Equal(20, presets.Count);
|
||||
Assert.All(presets, p => Assert.True(File.Exists(p) || p.StartsWith("pack://")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate3_ProfileManager_PreservesAvatarImagePath_OnSaveAndReload_Audit()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "paca_test_avatars_" + Guid.NewGuid().ToString("N"));
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(tempDir);
|
||||
var manager = new ProfileManager(tempDir);
|
||||
var dummyImagePath = Path.Combine(tempDir, "custom_avatar.png");
|
||||
File.WriteAllBytes(dummyImagePath, new byte[] { 0x89, 0x50, 0x4E, 0x47 }); // minimal png header
|
||||
|
||||
var prof = manager.AddProfile("아바타 테스트", "#4F46E5", "\ue19f", dummyImagePath);
|
||||
Assert.Equal(dummyImagePath, prof.AvatarImagePath);
|
||||
|
||||
// 다시 로드하여 영속화 일치 검증
|
||||
var reloadedManager = new ProfileManager(tempDir);
|
||||
var reloadedProf = reloadedManager.GetProfile(prof.Id);
|
||||
Assert.NotNull(reloadedProf);
|
||||
Assert.Equal(dummyImagePath, reloadedProf.AvatarImagePath);
|
||||
|
||||
// 업데이트 시 이미지 변경 및 제거 검증
|
||||
reloadedManager.UpdateProfile(prof.Id, "이름 변경", "#059669", "\ue0d7", null);
|
||||
var clearedProf = reloadedManager.GetProfile(prof.Id);
|
||||
Assert.NotNull(clearedProf);
|
||||
Assert.Null(clearedProf.AvatarImagePath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
{
|
||||
try { Directory.Delete(tempDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Gate4_ProfileAvatarBadge_RendersImageBrush_WhenImagePathSet_Audit()
|
||||
{
|
||||
using var sta = StaDispatcher.Start();
|
||||
WpfTestApp.Ensure(sta.Dispatcher);
|
||||
var success = await sta.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 더미 16x16 PNG 비트맵 생성
|
||||
var rtb = new RenderTargetBitmap(16, 16, 96, 96, PixelFormats.Pbgra32);
|
||||
var dv = new DrawingVisual();
|
||||
using (var dc = dv.RenderOpen())
|
||||
{
|
||||
dc.DrawRectangle(Brushes.Indigo, null, new Rect(0, 0, 16, 16));
|
||||
}
|
||||
rtb.Render(dv);
|
||||
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(rtb));
|
||||
var tempPng = Path.Combine(Path.GetTempPath(), $"paca_avatar_render_{Guid.NewGuid():N}.png");
|
||||
using (var fs = File.Create(tempPng))
|
||||
{
|
||||
encoder.Save(fs);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var brush = ProfileAvatarHelper.CreateAvatarBrush(tempPng);
|
||||
Assert.NotNull(brush);
|
||||
Assert.Equal(Stretch.UniformToFill, brush.Stretch);
|
||||
|
||||
var badge = new System.Windows.Controls.Border
|
||||
{
|
||||
Width = 24,
|
||||
Height = 24,
|
||||
CornerRadius = new CornerRadius(12),
|
||||
Background = brush
|
||||
};
|
||||
badge.Measure(new Size(24, 24));
|
||||
badge.Arrange(new Rect(0, 0, 24, 24));
|
||||
badge.UpdateLayout();
|
||||
|
||||
var testRtb = new RenderTargetBitmap(24, 24, 96, 96, PixelFormats.Pbgra32);
|
||||
testRtb.Render(badge);
|
||||
var px = new byte[24 * 24 * 4];
|
||||
testRtb.CopyPixels(px, 24 * 4, 0);
|
||||
|
||||
// 중앙 픽셀(12, 12)이 Indigo 계열(투명이 아님)인지 확인
|
||||
int centerIdx = (12 * 24 + 12) * 4;
|
||||
return px[centerIdx + 3] > 0; // Alpha > 0
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { File.Delete(tempPng); } catch { }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.True(success);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".png", true)]
|
||||
[InlineData(".jpg", true)]
|
||||
[InlineData(".jpeg", true)]
|
||||
[InlineData(".webp", true)]
|
||||
[InlineData(".bmp", true)]
|
||||
[InlineData(".exe", false)]
|
||||
[InlineData(".bat", false)]
|
||||
[InlineData(".txt", false)]
|
||||
public void Gate5_ProfileAvatarUpload_ValidatesExtension_Audit(string ext, bool expectedValid)
|
||||
{
|
||||
var isValid = ProfileAvatarHelper.IsSupportedImageExtension(ext);
|
||||
Assert.Equal(expectedValid, isValid);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue