using Admin.NET.Plugin.AiDOP.Controllers;
using Admin.NET.Plugin.AiDOP.Infrastructure;
using Admin.NET.Plugin.AiDOP.Service;
using Admin.NET.Plugin.AiDOP.SmartOps;
using Xunit;
namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps;
public class SmartOpsTrustedScopeTests
{
private const long TenantA = 838257186181189;
private const long FactoryA = 838257186320453;
private const long FactoryB = 838257212858437;
/// 种子/脚本建的租户(如 Ai-DOP 演示公司),根组织与租户同号。
private const long SeededTenant = 1300000000777;
[Fact]
public void PlatformTenant_IsRejected()
{
Assert.Throws(() =>
SmartOpsTrustedScopeRules.EnsureBusinessTenant(SmartOpsTrustedScopeRules.PlatformTenantId));
Assert.Throws(() =>
SmartOpsTrustedScopeRules.EnsureBusinessTenant(0));
Assert.Throws(() =>
SmartOpsTrustedScopeRules.EnsureBusinessTenant(1));
}
[Fact]
public void BusinessTenant_IsAccepted()
{
SmartOpsTrustedScopeRules.EnsureBusinessTenant(TenantA);
}
[Theory]
[InlineData(838257186181189, 838257186320453, 1L, 838257186320453)]
[InlineData(838257186181189, 838257186320453, 838257212858437L, 838257186320453)]
[InlineData(838257186181189, 838257186320453, 838257186181189L, 838257186320453)]
[InlineData(838257186181189, 838257186320453, 838257186320453L, 838257186320453)]
public void ClientFactory_IsNeverUsedAsPermissionBoundary(
long tenantId, long orgId, long candidate, long expected)
{
Assert.Equal(expected, SmartOpsTrustedScopeRules.ResolveFactory(tenantId, orgId, candidate));
}
[Fact]
public void MissingOrg_IsRejected()
{
Assert.Throws(() =>
SmartOpsTrustedScopeRules.ResolveFactory(TenantA, 0, FactoryA));
}
///
/// 种子/SQL 脚本建的租户,其根组织 Id 与租户 Id 同号(Admin.NET 官方约定,
/// 见 SysTenantSeedData「Id=OrgId=DefaultTenantId」与 SysOrgSeedData 根组织)。
/// 该形态是「已配置组织」,不得当成「未配置」拒绝。
///
[Fact]
public void SeededTenant_WhoseRootOrgSharesTenantId_IsAccepted()
{
Assert.Equal(SeededTenant, SmartOpsTrustedScopeRules.ResolveFactory(SeededTenant, SeededTenant, null));
Assert.Equal(SeededTenant, SmartOpsTrustedScopeRules.ResolveFactory(SeededTenant, SeededTenant, FactoryA));
}
[Fact]
public void AuthorizedFactory_MatchesTenantOrgOnly()
{
Assert.True(SmartOpsTrustedScopeRules.IsAuthorizedFactory(TenantA, FactoryA, FactoryA));
Assert.False(SmartOpsTrustedScopeRules.IsAuthorizedFactory(TenantA, FactoryA, FactoryB));
}
[Fact]
public void OperationalFactory_AcceptsLegacyDefaultAndTrustedOrg()
{
Assert.True(SmartOpsOperationalFactory.Matches(TenantA, FactoryA, 1));
Assert.True(SmartOpsOperationalFactory.Matches(TenantA, FactoryA, FactoryA));
Assert.True(SmartOpsOperationalFactory.Matches(TenantA, FactoryA, 0));
Assert.True(SmartOpsOperationalFactory.Matches(TenantA, FactoryA, null));
Assert.False(SmartOpsOperationalFactory.Matches(TenantA, FactoryA, TenantA));
Assert.False(SmartOpsOperationalFactory.Matches(TenantA, FactoryA, FactoryB));
}
[Fact]
public void PlanNo_IsTenantScopedAndMonotonic()
{
var prefix = SmartOpsImprovementPlanNo.BuildPrefix(new DateTime(2026, 8, 17));
Assert.Equal("SOP-20260817-", prefix);
Assert.Equal("SOP-20260817-001", SmartOpsImprovementPlanNo.Next(prefix, Array.Empty()));
Assert.Equal(
"SOP-20260817-003",
SmartOpsImprovementPlanNo.Next(prefix, new[] { "SOP-20260817-001", "SOP-20260817-002" }));
}
[Fact]
public void ConcurrentPlanNo_DoesNotReuseExistingNumbers()
{
var prefix = "SOP-20260817-";
var existing = new List();
var generated = new HashSet();
for (var i = 0; i < 20; i++)
{
var next = SmartOpsImprovementPlanNo.Next(prefix, existing);
Assert.True(generated.Add(next));
existing.Add(next);
}
Assert.Equal(20, generated.Count);
Assert.Contains("SOP-20260817-020", generated);
}
[Fact]
public void DiagnosisAndImprovement_UseTrustedScopeNotRequestTenant()
{
var diagnosis = typeof(AidopKanbanController).GetMethod(nameof(AidopKanbanController.GetSmartDiagnosis));
Assert.NotNull(diagnosis);
var controllerSource = ReadSiblingSource(
typeof(AdoSmartOpsImprovementPlanController),
"AdoSmartOpsImprovementPlanController.cs");
var diagnosisSource = ReadSiblingSource(
typeof(AidopKanbanController),
"AidopKanbanController.Diagnosis.cs");
Assert.DoesNotContain("AidopTenantHelper.GetTenantId", controllerSource);
Assert.DoesNotContain("AidopTenantHelper.GetTenantId", diagnosisSource);
Assert.Contains("_trustedScope.ResolveAsync", controllerSource);
Assert.Contains("_trustedScope.ResolveAsync", diagnosisSource);
Assert.Contains("x.TenantId == plan.TenantId", controllerSource);
Assert.Contains("x.TenantId == tenantId", controllerSource);
Assert.DoesNotContain("[AllowAnonymous]", controllerSource);
Assert.DoesNotContain("FactoryId = input.FactoryId", controllerSource);
}
[Fact]
public void ApproverGuard_ClearsFiltersWhenResolvingRoleMembers()
{
var source = ReadSiblingSource(
typeof(SmartOpsImprovementApproverGuard),
"SmartOpsImprovementApproverGuard.cs");
Assert.Contains("Queryable()", source);
Assert.Contains(".ClearFilter()", source);
Assert.Contains("u.TenantId == tenantId", source);
}
[Fact]
public void ApprovalCallback_UpdatesWithTenantCondition()
{
var source = ReadSiblingSource(
typeof(SmartOpsImprovementBizHandler),
"SmartOpsImprovementBizHandler.cs");
Assert.Contains("x.TenantId == plan.TenantId", source);
Assert.DoesNotContain(".Where(x => x.Id == bizId)", source.Replace("x.Id == bizId && x.TenantId == plan.TenantId", ""));
}
private static string ReadSiblingSource(Type type, string fileName)
{
var root = FindRepoRoot();
var matches = Directory.GetFiles(root, fileName, SearchOption.AllDirectories)
.Where(path => path.Replace('\\', '/').Contains("/Admin.NET.Plugin.AiDOP/")
&& !path.Contains(".Tests"))
.ToList();
Assert.NotEmpty(matches);
return File.ReadAllText(matches[0]);
}
private static string FindRepoRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
if (File.Exists(Path.Combine(dir.FullName, "AGENTS.md")))
return dir.FullName;
dir = dir.Parent;
}
return Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", ".."));
}
}