#nullable enable using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; using NUnit.Framework; using ShrinkContext; namespace ShrinkContext.Tests { /// /// 余效应操作与介导访问(论文 3.2 / 算法 2、6): /// set 是可逆效应;代理介导的 get 拒绝未声明/未激活访问;隔离域派生独立绑定。 /// public class CoeffectAccessTests { private ShrinkContextRuntime _runtime = null!; private ShrinkCtx _ctx = null!; [SetUp] public void SetUp() { _runtime = new ShrinkContextRuntime(); _ctx = _runtime.RootContext; } [Test] public void Set_IsReversibleEffect_EarlyWithdrawAndContextDispose() { var handle = _ctx.Set("cfg", 42); Assert.IsTrue(_runtime.TryGetRaw(_ctx, "cfg", out var v1)); Assert.AreEqual(42, v1); // 早期手动撤回 TestAwait.Run(handle.DisposeAsync()); Assert.IsFalse(_runtime.TryGetRaw(_ctx, "cfg", out _)); // 再次安装后随上下文卸载自动撤回 _ctx.Set("cfg", 43); Assert.IsTrue(_runtime.TryGetRaw(_ctx, "cfg", out var v2)); Assert.AreEqual(43, v2); TestAwait.Run(_ctx.DisposeAsync()); Assert.IsFalse(_runtime.TryGetRaw(_ctx, "cfg", out _), "set 的逆操作必须移除绑定"); } [Test] public void MediatedGet_RejectsUndeclaredKey_InApply() { var provider = new ProviderComponent("provider", "real", "v"); var fiber = _runtime.Use(provider); Assert.AreEqual(ShrinkFiberState.Active, fiber.State); var greedy = new GreedyConsumerComponent("greedy", "real", "undeclared"); var greedyFiber = _runtime.Use(greedy); Assert.AreEqual(ShrinkFiberState.Inactive, greedyFiber.State); Assert.IsInstanceOf(greedyFiber.LastError, "未声明的依赖访问在访问点被拒绝,并使装载失败(算法 6 UNDECLARED_ACCESS)"); } [Test] public void MediatedGet_ThrowsWhenOwnKeyNotActivated() { var consumer = new ConsumerComponent("consumer", "missing"); var fiber = _runtime.Use(consumer); Assert.AreEqual(ShrinkFiberState.Inactive, fiber.State, "依赖缺失时组件保持非活动,而不是抛错(响应式余效应)"); Assert.Throws(() => fiber.Ctx.Get("missing")); } [Test] public void Isolate_DerivesIndependentRealm_RestoreByDiscard() { _ctx.Set("k", 1); Assert.IsTrue(_runtime.TryGetRaw(_ctx, "k", out var rootValue)); Assert.AreEqual(1, rootValue); var isolated = _ctx.Isolate("k", "test-realm"); // 隔离域内初始为空:逻辑键相同但 realm 不同 Assert.IsFalse(_runtime.TryGetRaw(isolated, "k", out _)); isolated.Set("k", 2); Assert.IsTrue(_runtime.TryGetRaw(isolated, "k", out var isolatedValue)); Assert.AreEqual(2, isolatedValue); Assert.IsTrue(_runtime.TryGetRaw(_ctx, "k", out var rootValueAfter)); Assert.AreEqual(1, rootValueAfter, "隔离域写入不得影响父上下文绑定"); // 派生上下文的恢复是隐式的:丢弃即恢复(论文定义 27 派生实现) TestAwait.Run(isolated.DisposeAsync()); Assert.IsFalse(_runtime.TryGetRaw(isolated, "k", out _)); Assert.IsTrue(_runtime.TryGetRaw(_ctx, "k", out var rootValueFinal)); Assert.AreEqual(1, rootValueFinal); } [Test] public void TypedKey_PreservesVersionedIdentityAcrossMediatedAccess() { var key = new ShrinkKey("test.package", "typed-service", 2); Assert.AreEqual("test.package/typed-service@v2", key.Id); _runtime.Use(new PolicyProviderComponent(key.Id), "typed-value"); var consumer = _runtime.Use(new PolicyConsumerComponent(key.Id)); Assert.AreEqual(ShrinkFiberState.Active, consumer.State); Assert.AreEqual("typed-value", consumer.Ctx.Get(key)); } [Test] public void Intercept_MergesRootToLeafAndChildMetadataWins() { const string key = "policy.service"; _runtime.Use(new PolicyProviderComponent(key), "value"); var consumer = _runtime.Use(new PolicyConsumerComponent(key)); Assert.AreEqual(ShrinkFiberState.Active, consumer.State); var parent = consumer.Ctx.Intercept(key, new Dictionary { ["prefix"] = "parent-", ["suffix"] = "-parent" }); var child = parent.Intercept(key, new Dictionary { ["suffix"] = "-child" }); Assert.AreEqual("parent-value-child", child.Get(key)); } /// 声明一个键却访问另一个未声明键的组件(验证能力介导纪律)。 private sealed class GreedyConsumerComponent : IShrinkComponent { private readonly string[] _inject; public GreedyConsumerComponent(string name, string declaredKey, string undeclaredKey) { Name = name; UndeclaredKey = undeclaredKey; _inject = new[] { declaredKey }; } public string Name { get; } public string UndeclaredKey { get; } public IReadOnlyList Inject => _inject; public IReadOnlyList Provide => Array.Empty(); public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Get(UndeclaredKey); return UniTask.CompletedTask; } } } }