워크스페이스 복원에 탐색기·메모장 상태와 적응형 실행 간격을 추가한다
- 파일 탐색기 현재 폴더 경로와 메모장 열린 파일 경로를 워크스페이스 스냅샷에 저장하고 복원 경로에 연결 - 브라우저/앱 공용 프로세스 명령줄 파서를 추가하고 패키지형 메모장 실행 fallback을 보강 - 복원 중 새 창 실행 사이에 CPU·메모리 부하 기반 적응형 지연과 설정 UI를 추가 - README와 DEVELOPMENT 개발 이력을 2026-04-15 17:41 (KST) 기준으로 갱신 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_workspace_app_restore\\ -p:IntermediateOutputPath=obj\\verify_workspace_app_restore\\ 경고 0 오류 0, dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter WorkspaceHandlerTests|ContextManagerTests|SettingsServiceTests -p:OutputPath=bin\\verify_workspace_app_restore_tests\\ -p:IntermediateOutputPath=obj\\verify_workspace_app_restore_tests\\ 54개 통과
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using AxCopilot.Core;
|
||||
using AxCopilot.Models;
|
||||
using FluentAssertions;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Core;
|
||||
@@ -154,4 +155,196 @@ public class ContextManagerTests
|
||||
|
||||
BrowserWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppLaunchPlan_CreatesExplorerWindowWithCapturedFolder()
|
||||
{
|
||||
using var folder1 = new TempDirectory();
|
||||
using var folder2 = new TempDirectory();
|
||||
|
||||
var state = new AppWindowState
|
||||
{
|
||||
Kind = "explorer",
|
||||
PrimaryPath = folder1.Path,
|
||||
Paths = [folder1.Path, folder2.Path]
|
||||
};
|
||||
|
||||
var plan = AppWorkspaceStateHelper.CreateLaunchPlan(
|
||||
@"C:\Windows\explorer.exe",
|
||||
state);
|
||||
|
||||
plan.Should().NotBeNull();
|
||||
plan!.Arguments.Should().ContainSingle()
|
||||
.Which.Should().Be(folder1.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppLaunchPlan_CreatesNotepadWindowWithCapturedFiles()
|
||||
{
|
||||
using var file1 = new TempFile(".txt");
|
||||
using var file2 = new TempFile(".log");
|
||||
|
||||
var state = new AppWindowState
|
||||
{
|
||||
Kind = "notepad",
|
||||
PrimaryPath = file1.Path,
|
||||
Paths = [file1.Path, file2.Path]
|
||||
};
|
||||
|
||||
var plan = AppWorkspaceStateHelper.CreateLaunchPlan(
|
||||
@"C:\Windows\System32\notepad.exe",
|
||||
state);
|
||||
|
||||
plan.Should().NotBeNull();
|
||||
plan!.Arguments.Should().ContainInOrder(file1.Path, file2.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppLaunchPlan_UsesSystemExecutableFallbackForPackagedNotepad()
|
||||
{
|
||||
using var file1 = new TempFile(".txt");
|
||||
|
||||
var state = new AppWindowState
|
||||
{
|
||||
Kind = "notepad",
|
||||
PrimaryPath = file1.Path,
|
||||
Paths = [file1.Path]
|
||||
};
|
||||
|
||||
var plan = AppWorkspaceStateHelper.CreateLaunchPlan(
|
||||
@"C:\Program Files\WindowsApps\Microsoft.WindowsNotepad_11.2401.32.0_x64__8wekyb3d8bbwe\Notepad.exe",
|
||||
state);
|
||||
|
||||
plan.Should().NotBeNull();
|
||||
plan!.FileName.Should().Be("notepad.exe");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppShouldLaunchNewWindow_WithCapturedStateAndDifferentTitle_ReturnsTrue()
|
||||
{
|
||||
using var folder = new TempDirectory();
|
||||
|
||||
var snapshot = new WindowSnapshot
|
||||
{
|
||||
Exe = @"C:\Windows\explorer.exe",
|
||||
Title = "문서 - 파일 탐색기",
|
||||
AppState = new AppWindowState
|
||||
{
|
||||
Kind = "explorer",
|
||||
PrimaryPath = folder.Path,
|
||||
Paths = [folder.Path]
|
||||
}
|
||||
};
|
||||
|
||||
var candidate = new ContextManager.WindowCandidate(
|
||||
new IntPtr(7),
|
||||
snapshot.Exe,
|
||||
"다운로드 - 파일 탐색기");
|
||||
|
||||
AppWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppShouldLaunchNewWindow_WithCapturedStateAndExactTitle_ReturnsFalse()
|
||||
{
|
||||
var snapshot = new WindowSnapshot
|
||||
{
|
||||
Exe = @"C:\Windows\System32\notepad.exe",
|
||||
Title = "todo.txt - 메모장",
|
||||
AppState = new AppWindowState
|
||||
{
|
||||
Kind = "notepad",
|
||||
PrimaryPath = @"C:\Temp\todo.txt",
|
||||
Paths = [@"C:\Temp\todo.txt"]
|
||||
}
|
||||
};
|
||||
|
||||
var candidate = new ContextManager.WindowCandidate(
|
||||
new IntPtr(8),
|
||||
snapshot.Exe,
|
||||
"todo.txt - 메모장");
|
||||
|
||||
AppWorkspaceStateHelper.ShouldLaunchNewWindow(snapshot, candidate).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateRestoreLaunchDelayMs_Disabled_ReturnsZero()
|
||||
{
|
||||
var settings = new LauncherSettings
|
||||
{
|
||||
EnableAdaptiveWorkspaceRestoreThrottle = false,
|
||||
WorkspaceRestoreBaseDelayMs = 250,
|
||||
WorkspaceRestoreMaxDelayMs = 1200
|
||||
};
|
||||
|
||||
var delay = ContextManager.CalculateRestoreLaunchDelayMs(settings, 85, 92, 3);
|
||||
|
||||
delay.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateRestoreLaunchDelayMs_HighLoadIncreasesDelay()
|
||||
{
|
||||
var settings = new LauncherSettings
|
||||
{
|
||||
EnableAdaptiveWorkspaceRestoreThrottle = true,
|
||||
WorkspaceRestoreBaseDelayMs = 250,
|
||||
WorkspaceRestoreMaxDelayMs = 1200
|
||||
};
|
||||
|
||||
var lowLoadDelay = ContextManager.CalculateRestoreLaunchDelayMs(settings, 20, 45, 1);
|
||||
var highLoadDelay = ContextManager.CalculateRestoreLaunchDelayMs(settings, 88, 91, 4);
|
||||
|
||||
lowLoadDelay.Should().Be(250);
|
||||
highLoadDelay.Should().BeGreaterThan(lowLoadDelay);
|
||||
highLoadDelay.Should().BeLessOrEqualTo(1200);
|
||||
}
|
||||
|
||||
private sealed class TempFile : IDisposable
|
||||
{
|
||||
public TempFile(string extension)
|
||||
{
|
||||
Path = System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), extension);
|
||||
File.WriteAllText(Path, "temp");
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(Path))
|
||||
File.Delete(Path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 테스트 정리 실패는 무시합니다.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TempDirectory : IDisposable
|
||||
{
|
||||
public TempDirectory()
|
||||
{
|
||||
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"axcopilot-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
Directory.Delete(Path, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 테스트 정리 실패는 무시합니다.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,21 @@ public class SettingsServiceTests
|
||||
new LauncherSettings().EnableBrowserSessionRestore.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LauncherSettings_DefaultAdaptiveWorkspaceRestoreThrottle_IsEnabled()
|
||||
{
|
||||
new LauncherSettings().EnableAdaptiveWorkspaceRestoreThrottle.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LauncherSettings_DefaultWorkspaceRestoreDelayRange_IsValid()
|
||||
{
|
||||
var settings = new LauncherSettings();
|
||||
|
||||
settings.WorkspaceRestoreBaseDelayMs.Should().Be(250);
|
||||
settings.WorkspaceRestoreMaxDelayMs.Should().BeGreaterOrEqualTo(settings.WorkspaceRestoreBaseDelayMs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppSettings_DefaultMonitorMismatch_IsWarn()
|
||||
{
|
||||
@@ -244,6 +259,39 @@ public class SettingsServiceTests
|
||||
"https://portal.example.com");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkspaceProfile_Serialization_PreservesAppState()
|
||||
{
|
||||
var profile = new WorkspaceProfile
|
||||
{
|
||||
Name = "앱 상태 프로필",
|
||||
Windows =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Exe = "explorer.exe",
|
||||
Title = "문서 - 파일 탐색기",
|
||||
AppState = new AppWindowState
|
||||
{
|
||||
Kind = "explorer",
|
||||
PrimaryPath = @"C:\Docs",
|
||||
ActivePathIndex = 0,
|
||||
Paths = [@"C:\Docs", @"C:\Docs\Archive"]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(profile, JsonOptions);
|
||||
var restored = JsonSerializer.Deserialize<WorkspaceProfile>(json, JsonOptions)!;
|
||||
|
||||
restored.Windows.Should().HaveCount(1);
|
||||
restored.Windows[0].AppState.Should().NotBeNull();
|
||||
restored.Windows[0].AppState!.Kind.Should().Be("explorer");
|
||||
restored.Windows[0].AppState!.PrimaryPath.Should().Be(@"C:\Docs");
|
||||
restored.Windows[0].AppState!.Paths.Should().ContainInOrder(@"C:\Docs", @"C:\Docs\Archive");
|
||||
}
|
||||
|
||||
// ─── ClipboardTransformer ────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user