using System; using System.Threading.Tasks; using Paca.Mobile.Core; using Xunit; namespace Paca.Tests.Mobile; /// /// Gate 143 ~ 146: 모바일 스마트 클립보드 스니퍼 & 원클릭 Send-to-PC 플로팅 시트 기계적 검증 /// public class MobileSmartClipboardSnifferGateTests { private class MockClipboardReader : IClipboardReader { private string? _clipboardText; public void SetText(string? text) => _clipboardText = text; public Task 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); } }