feat: 외부도구 활성화 인프라 — ISettingsStore(DPAPI) + ExternalProcessRunner
AI 키·외부 도구 경로를 안전 저장하고 외부 CLI를 일원화 실행하는 토대. 둘 다 순수 .NET, 실동작 테스트 검증. - DpapiSettingsStore: DPAPI(CurrentUser) 암호화로 %LOCALAPPDATA%에 저장, 평문 노출 없음 검증 - ExternalProcessRunner: stdout/stderr 수집 + 타임아웃 + 프로세스 트리 Kill (LibreOffice/FFmpeg/Ghostscript 공용) - 테스트 6개 추가 (암호화 round-trip, echo/비정상종료/타임아웃) — 총 29개 통과
This commit is contained in:
parent
d49ee6365f
commit
937d9e7878
4 changed files with 268 additions and 0 deletions
96
src/Everything2Everything.Tests/InfraTests.cs
Normal file
96
src/Everything2Everything.Tests/InfraTests.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Everything2Everything.Core;
|
||||
using Everything2Everything.Core.Converters;
|
||||
using Xunit;
|
||||
|
||||
namespace Everything2Everything.Tests;
|
||||
|
||||
public class SettingsStoreTests
|
||||
{
|
||||
private static string TempFile()
|
||||
=> Path.Combine(Path.GetTempPath(), "e2e_settings_" + Guid.NewGuid().ToString("N") + ".dat");
|
||||
|
||||
[Fact]
|
||||
public void Set_Get_Roundtrip_AndPersists()
|
||||
{
|
||||
var path = TempFile();
|
||||
try
|
||||
{
|
||||
var store = new DpapiSettingsStore(path);
|
||||
store.Set("openai.key", "sk-secret-123");
|
||||
Assert.Equal("sk-secret-123", store.Get("openai.key"));
|
||||
Assert.True(store.Contains("openai.key"));
|
||||
|
||||
// 새 인스턴스로 다시 로드 — 영속 + 복호화 확인
|
||||
var reloaded = new DpapiSettingsStore(path);
|
||||
Assert.Equal("sk-secret-123", reloaded.Get("openai.key"));
|
||||
}
|
||||
finally { try { File.Delete(path); } catch { } }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void File_IsEncrypted_NotPlaintext()
|
||||
{
|
||||
var path = TempFile();
|
||||
try
|
||||
{
|
||||
new DpapiSettingsStore(path).Set("k", "PLAINTEXT_MARKER");
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
var asText = System.Text.Encoding.UTF8.GetString(bytes);
|
||||
Assert.DoesNotContain("PLAINTEXT_MARKER", asText);
|
||||
}
|
||||
finally { try { File.Delete(path); } catch { } }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_DeletesKey()
|
||||
{
|
||||
var path = TempFile();
|
||||
try
|
||||
{
|
||||
var store = new DpapiSettingsStore(path);
|
||||
store.Set("k", "v");
|
||||
store.Remove("k");
|
||||
Assert.Null(store.Get("k"));
|
||||
Assert.False(store.Contains("k"));
|
||||
}
|
||||
finally { try { File.Delete(path); } catch { } }
|
||||
}
|
||||
}
|
||||
|
||||
public class ExternalProcessRunnerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Echo_CapturesStdout_AndExitZero()
|
||||
{
|
||||
var r = await ExternalProcessRunner.RunAsync(
|
||||
"cmd.exe", new[] { "/c", "echo", "hello_e2e" },
|
||||
timeout: TimeSpan.FromSeconds(10), cancellationToken: CancellationToken.None);
|
||||
Assert.True(r.Success);
|
||||
Assert.Equal(0, r.ExitCode);
|
||||
Assert.Contains("hello_e2e", r.StdOut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NonZeroExit_IsReported()
|
||||
{
|
||||
var r = await ExternalProcessRunner.RunAsync(
|
||||
"cmd.exe", new[] { "/c", "exit", "3" },
|
||||
timeout: TimeSpan.FromSeconds(10), cancellationToken: CancellationToken.None);
|
||||
Assert.False(r.Success);
|
||||
Assert.Equal(3, r.ExitCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Timeout_IsDetected_AndProcessKilled()
|
||||
{
|
||||
var r = await ExternalProcessRunner.RunAsync(
|
||||
"cmd.exe", new[] { "/c", "ping", "127.0.0.1", "-n", "10" },
|
||||
timeout: TimeSpan.FromMilliseconds(400), cancellationToken: CancellationToken.None);
|
||||
Assert.True(r.TimedOut);
|
||||
Assert.False(r.Success);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue