using System.Text.Json;
using Xunit;
using Xunit.Abstractions;
namespace Stardust.McpApiTests;
/// <summary>è¶Šæƒæµ‹è¯•:跨项目资æºè®¿é—®ã€æœªæŽˆæƒèµ„æºã€ç¦ç”¨/过期令牌ã€éžæˆå‘˜ç”¨æˆ·ã€‚
/// é¢å‘真实 WebApi(6681) /mcp 端点,æ–言 McpGuard çš„ -32003/-32001 è¯ä¹‰ï¼ˆSDK 远程错误ç ä¿ç•™åœ¨ ErrorCode)。</summary>
[Collection(nameof(McpApiTestCollection))]
public class McpAuthzTests : McpSdkTestBase
{
public McpAuthzTests(ITestOutputHelper output) : base(output) { }
private String FullToken => McpTestDataInitializer.Tokens["mcpapi-full"];
/// <summary>ä¼ªé€ Token:tools/list 被拒(-32001 æœªç»æŽˆæƒï¼‰</summary>
[Fact(DisplayName = "è¶Šæƒ-1ï¼šä¼ªé€ Token 被拒")]
public async Task FakeToken_Rejected()
{
await RunSdkAsync("sdmcp_fake_token_00000000000000000000000000000000", async client =>
{
var mpe = await AssertRejectedAsync(async () => await client.ListToolsAsync(), -32001);
Output.WriteLine($"✅ ä¼ªé€ Token被拒:{mpe.Message}");
});
}
/// <summary>ç¦ç”¨ä»¤ç‰Œï¼šè¢«æ‹’(-32001 令牌已ç¦ç”¨æˆ–已过期)</summary>
[Fact(DisplayName = "è¶Šæƒ-2:ç¦ç”¨ä»¤ç‰Œè¢«æ‹’")]
public async Task DisabledToken_Rejected()
{
var token = McpTestDataInitializer.Tokens["mcpapi-disabled"];
await RunSdkAsync(token, async client =>
{
var mpe = await AssertRejectedAsync(async () => await client.ListToolsAsync(), -32001);
Output.WriteLine($"✅ ç¦ç”¨ä»¤ç‰Œè¢«æ‹’:{mpe.Message}");
});
}
/// <summary>过期令牌:被拒(-32001 令牌已ç¦ç”¨æˆ–已过期)</summary>
[Fact(DisplayName = "è¶Šæƒ-3:过期令牌被拒")]
public async Task ExpiredToken_Rejected()
{
var token = McpTestDataInitializer.Tokens["mcpapi-expired"];
await RunSdkAsync(token, async client =>
{
var mpe = await AssertRejectedAsync(async () => await client.ListToolsAsync(), -32001);
Output.WriteLine($"✅ 过期令牌被拒:{mpe.Message}");
});
}
/// <summary>éžæˆå‘˜ç”¨æˆ·ä»¤ç‰Œï¼ˆUserId>0 ä¸”éž ProjectUser æˆå‘˜ï¼‰ï¼šä¸šåŠ¡å·¥å…·è¢«æ‹’ï¼ˆ-32003 未绑定项目æˆå‘˜ï¼‰</summary>
[Fact(DisplayName = "è¶Šæƒ-4:éžé¡¹ç›®æˆå‘˜ç”¨æˆ·ä»¤ç‰Œä¸šåŠ¡å·¥å…·è¢«æ‹’")]
public async Task NonMemberUserToken_BusinessRejected()
{
var token = McpTestDataInitializer.Tokens["mcpapi-nonmember"];
await RunSdkAsync(token, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_app_list", new Dictionary<String, Object?>()), -32003);
Output.WriteLine($"✅ éžæˆå‘˜ä»¤ç‰Œä¸šåŠ¡å·¥å…·è¢«æ‹’ï¼š{mpe.Message}");
});
}
/// <summary>未授æƒèµ„æºä»¤ç‰Œï¼ˆåªæŽˆæƒäº†é¡¹ç›®ï¼ŒæœªæŽˆæƒåº”用/节点):访问应用被拒(-32003 CheckResource)</summary>
[Fact(DisplayName = "è¶Šæƒ-5:未授æƒåº”用资æºçš„令牌访问应用被拒")]
public async Task UnauthorizedAppResource_Rejected()
{
var token = McpTestDataInitializer.Tokens["mcpapi-unauth"];
await RunSdkAsync(token, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_app_health", new Dictionary<String, Object?>
{
["app_id"] = McpTestDataInitializer.AppId,
}), -32003);
Output.WriteLine($"✅ 未授æƒåº”用被拒:{mpe.Message}");
});
}
/// <summary>跨项目资æºè®¿é—®ï¼šä¸»ä»¤ç‰Œç»‘项目4,访问项目1的应用(StarServer Id=1)应被拒(-32003)</summary>
[Fact(DisplayName = "è¶Šæƒ-6:跨项目访问他人应用被拒")]
public async Task CrossProject_App_Rejected()
{
// Id=1 是默认项目(项目1)下的 StarServer 应用(现有ç§åæ•°æ®ï¼Œä¸å±žäºŽæµ‹è¯•项目)
await RunSdkAsync(FullToken, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_app_health", new Dictionary<String, Object?>
{
["app_id"] = 1,
}), -32003);
Output.WriteLine($"✅ 跨项目应用被拒:{mpe.Message}");
});
}
/// <summary>跨项目节点访问:访问åˆå§‹åŒ–器建的「他人项目节点ã€(ProjectId=1) 应被拒(-32003)</summary>
[Fact(DisplayName = "è¶Šæƒ-7:跨项目访问他人节点被拒")]
public async Task CrossProject_Node_Rejected()
{
var crossNodeId = McpTestDataInitializer.CrossNodeId;
Assert.True(crossNodeId > 0, "åˆå§‹åŒ–器应已建他人项目节点(ProjectId=1)作为越æƒé¶");
await RunSdkAsync(FullToken, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_node_detail", new Dictionary<String, Object?>
{
["node_id"] = crossNodeId,
}), -32003);
Output.WriteLine($"✅ 跨项目节点被拒:{mpe.Message}");
});
}
/// <summary>跨项目é…置:访问项目1çš„é…置(Common Id=1)应被拒</summary>
[Fact(DisplayName = "è¶Šæƒ-8:跨项目访问他人é…置被拒")]
public async Task CrossProject_Config_Rejected()
{
await RunSdkAsync(FullToken, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_config_get", new Dictionary<String, Object?>
{
["config_id"] = 1,
}), -32003);
Output.WriteLine($"✅ 跨项目é…置被拒:{mpe.Message}");
});
}
/// <summary>跨项目部署:访问项目1的部署集(StarServer Id=1)应被拒</summary>
[Fact(DisplayName = "è¶Šæƒ-9:跨项目访问他人部署被拒")]
public async Task CrossProject_Deploy_Rejected()
{
await RunSdkAsync(FullToken, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_deploy_status", new Dictionary<String, Object?>
{
["deploy_id"] = 1,
}), -32003);
Output.WriteLine($"✅ 跨项目部署被拒:{mpe.Message}");
});
}
/// <summary>è¶Šæƒå†™æ“作:未授æƒèµ„æºçš„令牌调写工具也应被拒(-32003)</summary>
[Fact(DisplayName = "è¶Šæƒ-10:未授æƒèµ„æºä»¤ç‰Œå†™å·¥å…·è¢«æ‹’")]
public async Task UnauthorizedResource_Write_Rejected()
{
var token = McpTestDataInitializer.Tokens["mcpapi-unauth"];
await RunSdkAsync(token, async client =>
{
var mpe = await AssertRejectedAsync(async () =>
await client.CallToolAsync("stardust_config_set", new Dictionary<String, Object?>
{
["config_id"] = McpTestDataInitializer.ConfigId,
["configs"] = new Dictionary<String, Object?> { ["x"] = "y" },
}), -32003);
Output.WriteLine($"✅ 未授æƒå†™å·¥å…·è¢«æ‹’:{mpe.Message}");
});
}
}
|