테스트 회귀망 강화: 권한 모드/슬래시 카탈로그 L4 통합 검증 추가
Some checks failed
Release Gate / gate (push) Has been cancelled

- PermissionModeCatalogTests 추가: 글로벌/도구 정규화, 승인 필요 정책, 한국어 표시 라벨 검증

- PermissionModePresentationCatalogTests 추가: 권한 표면 순서와 unknown fallback(Default) 검증

- SlashCommandCatalogTests 추가: dev 전용 명령 필터링과 /compact,/permissions,/mcp 핵심 명령 등록 검증

- OperationModePolicyTests 보강: deny 패턴이 allow 패턴보다 우선되는 충돌 케이스 추가

- README.md, docs/DEVELOPMENT.md에 2026-04-04 13:40(KST) 기준 이력 반영
This commit is contained in:
2026-04-04 13:40:58 +09:00
parent d9169ed3ea
commit b1fa8f692a
6 changed files with 166 additions and 1 deletions

View File

@@ -222,7 +222,7 @@ public class MyHandler : IActionHandler
### v0.7.3 — AX Agent 권한 코어 재구성 + 입력 계층 정리
업데이트: 2026-04-04 13:32 (KST)
업데이트: 2026-04-04 13:40 (KST)
| 분류 | 내용 |
|------|------|
@@ -279,6 +279,7 @@ public class MyHandler : IActionHandler
| slash 조회 API 전환 | 내장 slash 매칭/조회 경로를 `SlashCommandCatalog.MatchBuiltinCommands`/`TryGetEntry`로 통일 |
| 권한 표시 카탈로그 분리 | 권한 모드 라벨/설명/아이콘/색을 `PermissionModePresentationCatalog`로 분리해 팝업 표면 기준을 단일화 |
| 탭별 설정 해석기 도입 | `AgentTabSettingsResolver`를 추가해 Cowork/Code 분기(검증 활성/Code 전용 도구 비활성)를 단일 경로로 정리 |
| L4 통합 회귀 보강 | `PermissionModeCatalogTests`/`PermissionModePresentationCatalogTests`/`SlashCommandCatalogTests`를 추가하고 deny 우선 규칙을 `OperationModePolicyTests`에 반영해 권한·슬래시 회귀망을 강화 |
| Slash palette 상태 분리 시작 | `ChatWindow`에 몰려 있던 slash 상태를 `SlashPaletteState`로 분리해 이후 Codex/Claude형 composer 개편 기반 마련 |
| 런처 이미지 미리보기 추가 | `#` 클립보드 이미지 항목에서 `Shift+Enter`로 전용 미리보기 창을 열고, 줌·원본 해상도 확인·PNG/JPEG/BMP 저장·클립보드 복사를 지원 |
| 검증 | `dotnet build` 경고 0 / 오류 0, `dotnet test` 436 passed / 0 failed |

View File

@@ -3367,3 +3367,29 @@ else:
### 4) 품질 게이트
- dotnet build src/AxCopilot/AxCopilot.csproj -p:UseSharedCompilation=false -nodeReuse:false 통과 (경고 0, 오류 0).
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --filter "FullyQualifiedName~AgentTabSettingsResolverTests|FullyQualifiedName~ChatWindowSlashPolicyTests|FullyQualifiedName~OperationModeReadinessTests" 통과 (49 passed, 0 failed).
## 2026-04-04 추가 진행 기록 (연속 실행 29차: 권한/슬래시 L4 통합 회귀 보강)
업데이트: 2026-04-04 13:40 (KST)
### 1) 권한 모드 카탈로그 테스트 추가
- 신규 `PermissionModeCatalogTests`:
- 글로벌/도구 권한 정규화 매핑 검증
- 사용자 승인 필요 여부 정책 검증
- 권한 모드 표시 라벨(활용하지 않음/소극 활용/적극 활용/계획 중심/완전 자동/질문 없이 진행) 검증
- 신규 `PermissionModePresentationCatalogTests`:
- 권한 표면 순서(Deny→Default→AcceptEdits→Plan→BypassPermissions→DontAsk) 검증
- 미정의 모드 fallback이 `Default`로 수렴하는지 검증
### 2) 권한 규칙 우선순위 회귀 보강
- `OperationModePolicyTests`에 deny/allow 패턴 충돌 케이스 추가:
- `process@git push * = deny`가 `process@git * = acceptedits`보다 우선 적용되는지 검증
### 3) slash 카탈로그 회귀 보강
- 신규 `SlashCommandCatalogTests`:
- Chat 탭에서 dev 전용 명령(`/review`)이 숨겨지는지 검증
- 핵심 parity 명령(`/compact`, `/permissions`, `/mcp`)의 카탈로그 등록 검증
### 4) 품질 게이트
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Debug -p:UseSharedCompilation=false -nodeReuse:false` 통과 (경고 0, 오류 0).
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --filter "FullyQualifiedName~PermissionModeCatalogTests|FullyQualifiedName~PermissionModePresentationCatalogTests|FullyQualifiedName~SlashCommandCatalogTests|FullyQualifiedName~OperationModePolicyTests|FullyQualifiedName~OperationModeReadinessTests|FullyQualifiedName~ChatWindowSlashPolicyTests"` 통과 (88 passed, 0 failed).

View File

@@ -103,6 +103,23 @@ public class OperationModePolicyTests
askCalled.Should().BeFalse();
}
[Fact]
public void AgentContext_GetEffectiveToolPermission_DenyPatternPrecedesAllowPattern()
{
var context = new AgentContext
{
Permission = "AcceptEdits",
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["process@git *"] = "acceptedits",
["process@git push *"] = "deny",
}
};
context.GetEffectiveToolPermission("process", "git status").Should().Be("Default");
context.GetEffectiveToolPermission("process", "git push origin main").Should().Be("Deny");
}
[Fact]
public void AgentContext_GetEffectiveToolPermission_AcceptEditsAllowsWriteButKeepsProcessPrompted()
{

View File

@@ -0,0 +1,61 @@
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class PermissionModeCatalogTests
{
[Theory]
[InlineData(null, PermissionModeCatalog.Default)]
[InlineData("", PermissionModeCatalog.Default)]
[InlineData("ask", PermissionModeCatalog.Default)]
[InlineData("auto", PermissionModeCatalog.AcceptEdits)]
[InlineData("accept", PermissionModeCatalog.AcceptEdits)]
[InlineData("plan", PermissionModeCatalog.Plan)]
[InlineData("bypass", PermissionModeCatalog.BypassPermissions)]
[InlineData("dontask", PermissionModeCatalog.DontAsk)]
[InlineData("deny", PermissionModeCatalog.Deny)]
[InlineData("unknown", PermissionModeCatalog.Default)]
public void NormalizeGlobalMode_ShouldMapExpectedModes(string? input, string expected)
{
PermissionModeCatalog.NormalizeGlobalMode(input).Should().Be(expected);
}
[Theory]
[InlineData("ask", PermissionModeCatalog.Default)]
[InlineData("auto", PermissionModeCatalog.AcceptEdits)]
[InlineData("plan", PermissionModeCatalog.Plan)]
[InlineData("bypass", PermissionModeCatalog.BypassPermissions)]
[InlineData("dontask", PermissionModeCatalog.DontAsk)]
[InlineData("deny", PermissionModeCatalog.Deny)]
[InlineData("unknown", PermissionModeCatalog.Default)]
public void NormalizeToolOverride_ShouldMapExpectedModes(string? input, string expected)
{
PermissionModeCatalog.NormalizeToolOverride(input).Should().Be(expected);
}
[Theory]
[InlineData("ask", true)]
[InlineData("auto", false)]
[InlineData("acceptedits", false)]
[InlineData("bypassPermissions", false)]
[InlineData("dontAsk", false)]
[InlineData("deny", false)]
public void RequiresUserApproval_ShouldMatchPolicy(string? input, bool expected)
{
PermissionModeCatalog.RequiresUserApproval(input).Should().Be(expected);
}
[Theory]
[InlineData(PermissionModeCatalog.Deny, "활용하지 않음")]
[InlineData(PermissionModeCatalog.Default, "소극 활용")]
[InlineData(PermissionModeCatalog.AcceptEdits, "적극 활용")]
[InlineData(PermissionModeCatalog.Plan, "계획 중심")]
[InlineData(PermissionModeCatalog.BypassPermissions, "완전 자동")]
[InlineData(PermissionModeCatalog.DontAsk, "질문 없이 진행")]
public void ToDisplayLabel_ShouldReturnKoreanLabel(string mode, string expected)
{
PermissionModeCatalog.ToDisplayLabel(mode).Should().Be(expected);
}
}

View File

@@ -0,0 +1,29 @@
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class PermissionModePresentationCatalogTests
{
[Fact]
public void Ordered_ShouldMatchExpectedModeOrder()
{
PermissionModePresentationCatalog.Ordered.Select(x => x.Mode).Should().ContainInOrder(
[
PermissionModeCatalog.Deny,
PermissionModeCatalog.Default,
PermissionModeCatalog.AcceptEdits,
PermissionModeCatalog.Plan,
PermissionModeCatalog.BypassPermissions,
PermissionModeCatalog.DontAsk,
]);
}
[Fact]
public void Resolve_ShouldFallbackToDefaultPresentation_OnUnknownMode()
{
var resolved = PermissionModePresentationCatalog.Resolve("unknown-mode");
resolved.Mode.Should().Be(PermissionModeCatalog.Default);
}
}

View File

@@ -0,0 +1,31 @@
using AxCopilot.Views;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Views;
public class SlashCommandCatalogTests
{
[Fact]
public void MatchBuiltinCommands_ShouldFilterDevCommandsInChatTab()
{
var chatMatches = SlashCommandCatalog.MatchBuiltinCommands("/rev", isDevTab: false);
var devMatches = SlashCommandCatalog.MatchBuiltinCommands("/rev", isDevTab: true);
chatMatches.Should().BeEmpty();
devMatches.Should().ContainSingle(x => x.Cmd == "/review");
}
[Fact]
public void Catalog_ShouldContainCoreParityCommands()
{
SlashCommandCatalog.TryGetEntry("/compact", out var compactEntry).Should().BeTrue();
compactEntry.SystemPrompt.Should().Be("__COMPACT__");
SlashCommandCatalog.TryGetEntry("/permissions", out var permissionEntry).Should().BeTrue();
permissionEntry.SystemPrompt.Should().Be("__PERMISSIONS__");
SlashCommandCatalog.TryGetEntry("/mcp", out var mcpEntry).Should().BeTrue();
mcpEntry.SystemPrompt.Should().Be("__MCP__");
}
}