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
124
tests/Paca.Tests/Mobile/MobileSmartClipboardSnifferGateTests.cs
Normal file
124
tests/Paca.Tests/Mobile/MobileSmartClipboardSnifferGateTests.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Mobile.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// Gate 143 ~ 146: 모바일 스마트 클립보드 스니퍼 & 원클릭 Send-to-PC 플로팅 시트 기계적 검증
|
||||
/// </summary>
|
||||
public class MobileSmartClipboardSnifferGateTests
|
||||
{
|
||||
private class MockClipboardReader : IClipboardReader
|
||||
{
|
||||
private string? _clipboardText;
|
||||
|
||||
public void SetText(string? text) => _clipboardText = text;
|
||||
|
||||
public Task<string?> GetTextAsync() => Task.FromResult(_clipboardText);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://youtu.be/dQw4w9WgXcQ", true, "YouTube")]
|
||||
[InlineData("https://www.youtube.com/shorts/abcdef123", true, "YouTube Shorts")]
|
||||
[InlineData("https://www.tiktok.com/@user/video/7123456789", true, "TikTok")]
|
||||
[InlineData("https://x.com/user/status/1234567890?s=20", true, "X (Twitter)")]
|
||||
[InlineData("https://twitter.com/user/status/1234567890", true, "X (Twitter)")]
|
||||
[InlineData("https://www.instagram.com/reel/C123456/", true, "Instagram Reel")]
|
||||
[InlineData("https://chzzk.naver.com/video/98765", true, "CHZZK")]
|
||||
[InlineData("https://vod.sooplive.co.kr/player/112233", true, "SOOP")]
|
||||
[InlineData("https://stream.example.com/live/playlist.m3u8", true, "Direct HLS Stream")]
|
||||
[InlineData("https://cdn.example.com/videos/bunny.mp4?token=abc", true, "Direct Video File")]
|
||||
[InlineData("안녕하세요 일반 텍스트 메모입니다.", false, "None")]
|
||||
[InlineData("https://www.google.com/search?q=csharp", false, "None")]
|
||||
[InlineData("", false, "None")]
|
||||
[InlineData(null, false, "None")]
|
||||
public void Gate143_SmartClipboard_Detects_Supported_Video_Platforms(string? inputUrl, bool expectedIsVideo, string expectedPlatform)
|
||||
{
|
||||
var result = SmartClipboardSniffer.AnalyzeUrl(inputUrl);
|
||||
|
||||
Assert.Equal(expectedIsVideo, result.IsVideoUrl);
|
||||
Assert.Equal(expectedPlatform, result.DetectedPlatform);
|
||||
if (expectedIsVideo)
|
||||
{
|
||||
Assert.False(string.IsNullOrWhiteSpace(result.NormalizedUrl));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Gate144_SmartClipboard_Deduplication_And_Cooldown()
|
||||
{
|
||||
var mockClipboard = new MockClipboardReader();
|
||||
var sniffer = new SmartClipboardSniffer(mockClipboard, cacheTtl: TimeSpan.FromMinutes(5));
|
||||
|
||||
// 1. 첫 복사 시도 -> 감지 성공
|
||||
mockClipboard.SetText("https://youtu.be/dQw4w9WgXcQ?si=tracking123");
|
||||
var firstResult = await sniffer.CheckClipboardAsync();
|
||||
Assert.NotNull(firstResult);
|
||||
Assert.True(firstResult.IsVideoUrl);
|
||||
|
||||
// 2. 동일한 클립보드 상태로 즉시 재검사 -> 캐시/중복 방지로 null 반환 (불필요한 팝업 방지)
|
||||
var secondResult = await sniffer.CheckClipboardAsync();
|
||||
Assert.Null(secondResult);
|
||||
|
||||
// 3. 새로운 비디오 URL로 클립보드 변경 -> 감지 성공
|
||||
mockClipboard.SetText("https://www.tiktok.com/@creator/video/998877");
|
||||
var thirdResult = await sniffer.CheckClipboardAsync();
|
||||
Assert.NotNull(thirdResult);
|
||||
Assert.True(thirdResult.IsVideoUrl);
|
||||
Assert.Equal("TikTok", thirdResult.DetectedPlatform);
|
||||
|
||||
// 4. 소비(Dismiss/Sent) 처리 후 재요청 시 다시 차단 유지 확인
|
||||
sniffer.DismissCurrent();
|
||||
var dismissedResult = await sniffer.CheckClipboardAsync();
|
||||
Assert.Null(dismissedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate145_SmartClipboard_Url_Normalization()
|
||||
{
|
||||
var dirtyUrl = "https://youtu.be/dQw4w9WgXcQ?si=junkTrackingToken&utm_source=app&fbclid=XYZ123";
|
||||
var result = SmartClipboardSniffer.AnalyzeUrl(dirtyUrl);
|
||||
|
||||
Assert.True(result.IsVideoUrl);
|
||||
// 트래킹 파라미터는 정규화되어 제거됨
|
||||
Assert.DoesNotContain("si=junkTrackingToken", result.NormalizedUrl);
|
||||
Assert.DoesNotContain("utm_source=app", result.NormalizedUrl);
|
||||
Assert.DoesNotContain("fbclid=XYZ123", result.NormalizedUrl);
|
||||
Assert.Equal("https://youtu.be/dQw4w9WgXcQ", result.NormalizedUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Gate146_SmartClipboard_Banner_Action_Dispatches_To_Active_Connection()
|
||||
{
|
||||
var mockClipboard = new MockClipboardReader();
|
||||
var sniffer = new SmartClipboardSniffer(mockClipboard);
|
||||
|
||||
mockClipboard.SetText("https://x.com/elonmusk/status/987654321");
|
||||
var result = await sniffer.CheckClipboardAsync();
|
||||
Assert.NotNull(result);
|
||||
|
||||
bool sentToPcCalled = false;
|
||||
string? sentUrl = null;
|
||||
string? sentTarget = null;
|
||||
|
||||
var bannerAction = new SmartClipboardBannerAction(
|
||||
Result: result,
|
||||
SendToPcAction: async (url, targetDevice) =>
|
||||
{
|
||||
sentToPcCalled = true;
|
||||
sentUrl = url;
|
||||
sentTarget = targetDevice;
|
||||
await Task.Yield();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
var success = await bannerAction.ExecuteSendToPcAsync("My-Desktop-PC");
|
||||
Assert.True(success);
|
||||
Assert.True(sentToPcCalled);
|
||||
Assert.Equal(result.NormalizedUrl, sentUrl);
|
||||
Assert.Equal("My-Desktop-PC", sentTarget);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue