Files
AX-Copilot-Codex/src/AxCopilot.Tests/Services/OperationModePolicyTests.cs
lacvet b1fa8f692a
Some checks failed
Release Gate / gate (push) Has been cancelled
테스트 회귀망 강화: 권한 모드/슬래시 카탈로그 L4 통합 검증 추가
- PermissionModeCatalogTests 추가: 글로벌/도구 정규화, 승인 필요 정책, 한국어 표시 라벨 검증

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

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

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

- README.md, docs/DEVELOPMENT.md에 2026-04-04 13:40(KST) 기준 이력 반영
2026-04-04 13:40:58 +09:00

194 lines
6.6 KiB
C#

using AxCopilot.Models;
using AxCopilot.Services;
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class OperationModePolicyTests
{
[Fact]
public void Normalize_DefaultsToInternalWhenMissingOrUnknown()
{
OperationModePolicy.Normalize(null).Should().Be(OperationModePolicy.InternalMode);
OperationModePolicy.Normalize("").Should().Be(OperationModePolicy.InternalMode);
OperationModePolicy.Normalize("unknown").Should().Be(OperationModePolicy.InternalMode);
}
[Fact]
public void Normalize_MapsExternalMode()
{
OperationModePolicy.Normalize("external").Should().Be(OperationModePolicy.ExternalMode);
OperationModePolicy.Normalize("EXTERNAL").Should().Be(OperationModePolicy.ExternalMode);
}
[Fact]
public void IsBlockedAgentToolInInternalMode_BlocksExternalHttpAndUrlOpen()
{
OperationModePolicy.IsBlockedAgentToolInInternalMode("http_tool", "https://example.com").Should().BeTrue();
OperationModePolicy.IsBlockedAgentToolInInternalMode("open_external", "https://example.com").Should().BeTrue();
OperationModePolicy.IsBlockedAgentToolInInternalMode("open_external", @"E:\work\report.html").Should().BeFalse();
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_BlocksRestrictedToolsInInternalMode()
{
var context = new AgentContext
{
OperationMode = OperationModePolicy.InternalMode,
Permission = "AcceptEdits"
};
var blocked = await context.CheckToolPermissionAsync("http_tool", "https://example.com");
var allowed = await context.CheckToolPermissionAsync("file_read", @"E:\work\a.txt");
blocked.Should().BeFalse();
allowed.Should().BeTrue();
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_AllowsRestrictedToolsInExternalMode()
{
var context = new AgentContext
{
OperationMode = OperationModePolicy.ExternalMode,
Permission = "AcceptEdits"
};
var allowed = await context.CheckToolPermissionAsync("http_tool", "https://example.com");
allowed.Should().BeTrue();
}
[Fact]
public void AgentContext_GetEffectiveToolPermission_PrefersPatternRule()
{
var context = new AgentContext
{
Permission = "Default",
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["process"] = "deny",
["process@git *"] = "acceptedits",
["*@*.md"] = "default",
}
};
context.GetEffectiveToolPermission("process", "git status").Should().Be("Default");
context.GetEffectiveToolPermission("process", "powershell -NoProfile").Should().Be("Deny");
context.GetEffectiveToolPermission("file_read", @"E:\work\README.md").Should().Be("AcceptEdits");
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_UsesPatternRuleWithoutPrompt()
{
var askCalled = false;
var context = new AgentContext
{
OperationMode = OperationModePolicy.ExternalMode,
Permission = "Default",
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["process@git *"] = "bypassPermissions",
},
AskPermission = (_, _) =>
{
askCalled = true;
return Task.FromResult(false);
}
};
var allowed = await context.CheckToolPermissionAsync("process", "git status");
allowed.Should().BeTrue();
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()
{
var context = new AgentContext
{
Permission = "AcceptEdits"
};
context.GetEffectiveToolPermission("process", "git status").Should().Be("Default");
context.GetEffectiveToolPermission("file_write", @"E:\work\out.txt").Should().Be("AcceptEdits");
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_PlanModeBlocksWriteButAllowsRead()
{
var askCalled = false;
var context = new AgentContext
{
OperationMode = OperationModePolicy.ExternalMode,
Permission = "Plan",
AskPermission = (_, _) =>
{
askCalled = true;
return Task.FromResult(true);
}
};
var writeAllowed = await context.CheckToolPermissionAsync("file_write", @"E:\work\out.txt");
var readAllowed = await context.CheckToolPermissionAsync("file_read", @"E:\work\in.txt");
writeAllowed.Should().BeFalse();
readAllowed.Should().BeTrue();
askCalled.Should().BeFalse();
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_BypassPermissionsSkipsPrompt()
{
var askCalled = false;
var context = new AgentContext
{
OperationMode = OperationModePolicy.ExternalMode,
Permission = "BypassPermissions",
AskPermission = (_, _) =>
{
askCalled = true;
return Task.FromResult(false);
}
};
var allowed = await context.CheckToolPermissionAsync("process", "git status");
allowed.Should().BeTrue();
askCalled.Should().BeFalse();
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_DenyModeBlocksWriteButAllowsRead()
{
var context = new AgentContext
{
OperationMode = OperationModePolicy.ExternalMode,
Permission = "Deny"
};
var writeAllowed = await context.CheckToolPermissionAsync("file_write", @"E:\work\out.txt");
var readAllowed = await context.CheckToolPermissionAsync("file_read", @"E:\work\in.txt");
writeAllowed.Should().BeFalse();
readAllowed.Should().BeTrue();
}
}