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:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,118 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using Paca.App.Utils;
using Paca.Tests.Infrastructure;
using Xunit;
namespace Paca.Tests.App;
public class DownloadFolderOpenGateTests
{
[Fact]
public void Gate480_ExplorerLauncher_Arguments_HaveSelectOutsideQuotes_AndUseShellExecuteTrue()
{
// DoD: 공백 및 한글/특수문자가 포함된 다운로드 파일명에 대해
// explorer.exe 인자 구성 시 /select, 가 큰따옴표 바깥에 위치하고 UseShellExecute = true 여야 함
var testDir = Path.Combine(Path.GetTempPath(), "PacaTest_Gate480_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(testDir);
try
{
var testFile = Path.Combine(testDir, "(13) 손만 잡아도 2500만 원 순삭_ 베트남 국제결혼의 비밀!.mp4");
File.WriteAllText(testFile, "dummy video data");
var psi = ExplorerLauncher.BuildProcessStartInfo(testFile, testDir);
Assert.NotNull(psi);
Assert.True(psi.UseShellExecute, "UseShellExecute must be true for Windows Shell explorer invocation");
Assert.Equal("explorer.exe", psi.FileName, ignoreCase: true);
// 중요: "/select,C:\..." 형태는 Windows explorer.exe 가 옵션으로 인식하지 못하고 종료코드 1로 실패함
// 올바른 형식: /select,"C:\..."
Assert.StartsWith("/select,\"", psi.Arguments);
Assert.EndsWith("\"", psi.Arguments);
Assert.False(psi.Arguments.StartsWith("\"/select,"), "Arguments must NOT enclose /select within quotes");
}
finally
{
try { Directory.Delete(testDir, true); } catch { }
}
}
[Fact]
public void Gate481_ExplorerLauncher_FallbackToDirectory_WhenFileDoesNotExist()
{
var testDir = Path.Combine(Path.GetTempPath(), "PacaTest_Gate481_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(testDir);
try
{
var nonExistentFile = Path.Combine(testDir, "deleted_video.mp4");
var psi = ExplorerLauncher.BuildProcessStartInfo(nonExistentFile, testDir);
Assert.NotNull(psi);
Assert.True(psi.UseShellExecute);
// 파일이 없으면 상위 폴더(testDir) 자체를 직접 열도록 구성
Assert.Equal(Path.GetFullPath(testDir), Path.GetFullPath(psi.FileName), ignoreCase: true);
Assert.True(string.IsNullOrEmpty(psi.Arguments));
}
finally
{
try { Directory.Delete(testDir, true); } catch { }
}
}
[Fact]
public void Gate482_ExplorerLauncher_RelativePath_ResolvedWithOutputFolder()
{
var testDir = Path.Combine(Path.GetTempPath(), "PacaTest_Gate482_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(testDir);
try
{
const string fileName = "relative_clip.mp4";
var fullPath = Path.Combine(testDir, fileName);
File.WriteAllText(fullPath, "content");
// 상대 경로가 넘어왔을 때 OutputFolder 기준으로 전체 경로 해석 검증
var psi = ExplorerLauncher.BuildProcessStartInfo(fileName, testDir);
Assert.NotNull(psi);
Assert.True(psi.UseShellExecute);
Assert.Equal("explorer.exe", psi.FileName, ignoreCase: true);
Assert.Contains(fullPath, psi.Arguments);
Assert.StartsWith("/select,\"", psi.Arguments);
}
finally
{
try { Directory.Delete(testDir, true); } catch { }
}
}
[Fact]
public void Gate483_OpenInExplorer_SourceCode_DoesNotUseArgumentListWithUseShellExecuteFalse()
{
// DoD: MainWindow.xaml.cs의 OpenInExplorer가 ArgumentList.Add(/select...) + UseShellExecute = false 조합을 사용하지 않아야 함
var mwCs = File.ReadAllText(SourceTree.AppPath("MainWindow.xaml.cs"));
Assert.DoesNotContain("psi.ArgumentList.Add($\"/select,{selectFile}\");", mwCs);
Assert.DoesNotContain("new System.Diagnostics.ProcessStartInfo(\"explorer.exe\") { UseShellExecute = false }", mwCs);
}
[Fact]
public void Gate484_ExplorerLauncher_CreatesDirectory_IfOutputFolderDoesNotExist()
{
var nonExistentDir = Path.Combine(Path.GetTempPath(), "PacaTest_Gate484_AutoCreate_" + Guid.NewGuid().ToString("N"));
Assert.False(Directory.Exists(nonExistentDir));
try
{
var psi = ExplorerLauncher.BuildProcessStartInfo(null, nonExistentDir);
Assert.NotNull(psi);
Assert.True(Directory.Exists(nonExistentDir), "BuildProcessStartInfo or Launcher must ensure the output folder exists");
}
finally
{
try { Directory.Delete(nonExistentDir, true); } catch { }
}
}
}