diff --git a/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs index d1bd75b7..91f0e8a2 100644 --- a/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs @@ -36,6 +36,8 @@ namespace SysML2.NET.Tests.Extend using SysML2.NET.Core.Systems.States; using SysML2.NET.Extensions; + using PocoFeature = SysML2.NET.Core.POCO.Core.Features.Feature; + [TestFixture] public class AcceptActionUsageExtensionsTestFixture { @@ -85,10 +87,47 @@ public void VerifyComputePayloadParameter() { Assert.That(() => ((IAcceptActionUsage)null).ComputePayloadParameter(), Throws.TypeOf()); - var accept = new AcceptActionUsage(); + // OCL: payloadParameter = if parameter->isEmpty() then null else parameter->first() endif + // where parameter = the directed owned features (Step::parameter → Type::directedFeature). + using (Assert.EnterMultipleScope()) + { + // Branch 1 — bare AcceptActionUsage: no owned features → parameter is empty → null. + var emptyAccept = new AcceptActionUsage(); + + Assert.That(emptyAccept.ComputePayloadParameter(), Is.Null); + + // Branch 2 — direction filter: a single owned ReferenceUsage with NO Direction is not a + // directed feature (DirectionOf == null) → parameter is empty → null. + var nonDirectedAccept = new AcceptActionUsage(); + var nonDirectedParameter = new ReferenceUsage(); + nonDirectedAccept.AssignOwnership(new FeatureMembership(), nonDirectedParameter); + + Assert.That(nonDirectedAccept.ComputePayloadParameter(), Is.Null); + + // Branch 3 — populated: a single directed ReferenceUsage parameter → parameter->first() is it. + var populatedAccept = new AcceptActionUsage(); + var payloadParameter = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + populatedAccept.AssignOwnership(new FeatureMembership(), payloadParameter); + + Assert.That(populatedAccept.ComputePayloadParameter(), Is.SameAs(payloadParameter)); + + // Branch 4 — `as IReferenceUsage` discrimination: the first directed parameter is a plain + // Feature (not an IReferenceUsage) → parameter->first() as IReferenceUsage is null. + var wrongTypeAccept = new AcceptActionUsage(); + var directedNonReference = new PocoFeature { Direction = FeatureDirectionKind.In }; + wrongTypeAccept.AssignOwnership(new FeatureMembership(), directedNonReference); + + Assert.That(wrongTypeAccept.ComputePayloadParameter(), Is.Null); + + // Branch 5 — ordering: two directed ReferenceUsage parameters → parameter->first() is the first. + var orderedAccept = new AcceptActionUsage(); + var firstParameter = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + var secondParameter = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + orderedAccept.AssignOwnership(new FeatureMembership(), firstParameter); + orderedAccept.AssignOwnership(new FeatureMembership(), secondParameter); - // For Later: populated case depends on StepExtensions.ComputeParameter at SysML2.NET/Extend/StepExtensions.cs:71, which is still a stub. - Assert.That(() => accept.ComputePayloadParameter(), Throws.TypeOf()); + Assert.That(orderedAccept.ComputePayloadParameter(), Is.SameAs(firstParameter)); + } } [Test] diff --git a/SysML2.NET.Tests/Extend/FunctionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/FunctionExtensionsTestFixture.cs index c056b80c..f7e2b69f 100644 --- a/SysML2.NET.Tests/Extend/FunctionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/FunctionExtensionsTestFixture.cs @@ -1,50 +1,99 @@ // ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Extensions; [TestFixture] public class FunctionExtensionsTestFixture { [Test] - public void ComputeExpression_ThrowsNotSupportedException() + public void VerifyComputeResult() { - Assert.That(() => ((IFunction)null).ComputeExpression(), Throws.TypeOf()); + // Null subject: + Assert.That(() => ((IFunction)null).ComputeResult(), Throws.TypeOf()); + + // Empty: no ReturnParameterMembership → null. + var emptySubject = new Function(); + Assert.That(emptySubject.ComputeResult(), Is.Null); + + // Negative: a FeatureMembership that is NOT a ReturnParameterMembership → null. + var negativeSubject = new Function(); + var plainMembership = new FeatureMembership(); + var plainFeature = new Feature(); + negativeSubject.AssignOwnership(plainMembership, plainFeature); + Assert.That(negativeSubject.ComputeResult(), Is.Null); + + // Positive: one ReturnParameterMembership whose ownedMemberParameter is a Feature → that feature. + var subject = new Function(); + var resultFeature = new Feature(); + var returnParameterMembership = new ReturnParameterMembership(); + subject.AssignOwnership(returnParameterMembership, resultFeature); + Assert.That(subject.ComputeResult(), Is.SameAs(resultFeature)); + + // Two ReturnParameterMemberships → the FIRST is returned (OCL ->first()). + var secondResultFeature = new Feature(); + var secondReturnParameterMembership = new ReturnParameterMembership(); + subject.AssignOwnership(secondReturnParameterMembership, secondResultFeature); + Assert.That(subject.ComputeResult(), Is.SameAs(resultFeature)); } - + [Test] - public void ComputeIsModelLevelEvaluable_ThrowsNotSupportedException() + public void VerifyComputeExpression() { - Assert.That(() => ((IFunction)null).ComputeIsModelLevelEvaluable(), Throws.TypeOf()); + // Null subject: + Assert.That(() => ((IFunction)null).ComputeExpression(), Throws.TypeOf()); + + // Empty: no features → empty. + var emptySubject = new Function(); + Assert.That(emptySubject.ComputeExpression(), Is.Empty); + + // Kind filter: an Expression feature plus a non-Expression feature → only the Expression. + var subject = new Function(); + var expressionFeature = new Expression(); + var plainFeature = new Feature(); + subject.AssignOwnership(new FeatureMembership(), expressionFeature); + subject.AssignOwnership(new FeatureMembership(), plainFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeExpression(), Does.Contain(expressionFeature)); + Assert.That(subject.ComputeExpression(), Does.Not.Contain(plainFeature)); + Assert.That(subject.ComputeExpression(), Has.Count.EqualTo(1)); + } } - + [Test] - public void ComputeResult_ThrowsNotSupportedException() + public void VerifyComputeIsModelLevelEvaluable() { - Assert.That(() => ((IFunction)null).ComputeResult(), Throws.TypeOf()); + // For later: deferred — Kernel Functions Library registry membership test (see GitHub #322). + var subject = new Function(); + Assert.That(subject.ComputeIsModelLevelEvaluable, Throws.TypeOf()); } } } diff --git a/SysML2.NET.Tests/Extend/ReferenceUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/ReferenceUsageExtensionsTestFixture.cs index 28a7dda0..eea4620c 100644 --- a/SysML2.NET.Tests/Extend/ReferenceUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/ReferenceUsageExtensionsTestFixture.cs @@ -25,6 +25,7 @@ namespace SysML2.NET.Tests.Extend using NUnit.Framework; using SysML2.NET.Core.Core.Types; + using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Systems.Actions; using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; @@ -91,10 +92,10 @@ public void VerifyComputeRedefinedNamingFeatureOperation() Assert.That(secondParamNoTrigger.ComputeRedefinedNamingFeatureOperation(), Is.Null); - // Branch 5: owningType is an ITransitionUsage, self IS InputParameter(2), and - // triggerAction is non-empty → TriggerPayloadParameter() accesses triggerAction[0].payloadParameter - // → AcceptActionUsage.parameter → StepExtensions.ComputeParameter, which is still a stub. - // Assert the stub propagates rather than silently returning the wrong value. + // Branch 5: owningType is an ITransitionUsage, self IS InputParameter(2), and triggerAction is + // non-empty → TriggerPayloadParameter() = triggerAction->first().payloadParameter. The trigger + // AcceptActionUsage owns a directed ReferenceUsage as its (payload) parameter → that ReferenceUsage + // is returned. var transitionUsageWithTrigger = new TransitionUsage(); var firstParamWithTrigger = new ReferenceUsage { Direction = FeatureDirectionKind.In }; var secondParamWithTrigger = new ReferenceUsage { Direction = FeatureDirectionKind.In }; @@ -103,11 +104,22 @@ public void VerifyComputeRedefinedNamingFeatureOperation() var triggerFeatureMembership = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; var triggerAcceptAction = new AcceptActionUsage(); + var triggerPayloadParameter = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + triggerAcceptAction.AssignOwnership(new FeatureMembership(), triggerPayloadParameter); transitionUsageWithTrigger.AssignOwnership(triggerFeatureMembership, triggerAcceptAction); Assert.That( - () => secondParamWithTrigger.ComputeRedefinedNamingFeatureOperation(), - Throws.TypeOf()); + secondParamWithTrigger.ComputeRedefinedNamingFeatureOperation(), + Is.SameAs(triggerPayloadParameter)); + + // Branch 6 (else path, positive): owningType is null so the transition branch is skipped and the + // result is self.oclAsType(Usage).namingFeature() = the redefinedFeature of the first owned + // Redefinition. Wire a Redefinition so the else branch surfaces a non-null naming Feature. + var redefiningSubject = new ReferenceUsage(); + var namingFeature = new ReferenceUsage(); + redefiningSubject.AssignOwnership(new Redefinition { RedefinedFeature = namingFeature }); + + Assert.That(redefiningSubject.ComputeRedefinedNamingFeatureOperation(), Is.SameAs(namingFeature)); } } } diff --git a/SysML2.NET.Tests/Extend/StepExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/StepExtensionsTestFixture.cs index 0a152e47..01c4664c 100644 --- a/SysML2.NET.Tests/Extend/StepExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/StepExtensionsTestFixture.cs @@ -1,44 +1,89 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.Core.Types; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Behaviors; + using SysML2.NET.Extensions; + + using Type = SysML2.NET.Core.POCO.Core.Types.Type; [TestFixture] public class StepExtensionsTestFixture { [Test] - public void ComputeBehavior_ThrowsNotSupportedException() + public void VerifyComputeBehavior() { - Assert.That(() => ((IStep)null).ComputeBehavior(), Throws.TypeOf()); + // Null subject: + Assert.That(() => ((IStep)null).ComputeBehavior(), Throws.TypeOf()); + + // Empty: no typings → empty. + var emptySubject = new Step(); + Assert.That(emptySubject.ComputeBehavior(), Is.Empty); + + // Kind filter: a Behavior type plus a non-Behavior type → only the Behavior. + var subject = new Step(); + var behavior = new Behavior(); + var plainType = new Type(); + subject.AssignOwnership(new FeatureTyping { Type = behavior }); + subject.AssignOwnership(new FeatureTyping { Type = plainType }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeBehavior(), Does.Contain(behavior)); + Assert.That(subject.ComputeBehavior(), Does.Not.Contain(plainType)); + Assert.That(subject.ComputeBehavior(), Has.Count.EqualTo(1)); + } } - + [Test] - public void ComputeParameter_ThrowsNotSupportedException() + public void VerifyComputeParameter() { - Assert.That(() => ((IStep)null).ComputeParameter(), Throws.TypeOf()); + // Null subject: + Assert.That(() => ((IStep)null).ComputeParameter(), Throws.TypeOf()); + + // Empty: no directed features → empty. + var emptySubject = new Step(); + Assert.That(emptySubject.ComputeParameter(), Is.Empty); + + // Directed features returned in order; a non-directed feature is excluded. + var subject = new Step(); + var firstParameter = new Feature { Direction = FeatureDirectionKind.In }; + var secondParameter = new Feature { Direction = FeatureDirectionKind.Out }; + var plainFeature = new Feature(); + subject.AssignOwnership(new FeatureMembership(), firstParameter); + subject.AssignOwnership(new FeatureMembership(), plainFeature); + subject.AssignOwnership(new FeatureMembership(), secondParameter); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeParameter(), Does.Not.Contain(plainFeature)); + Assert.That(subject.ComputeParameter(), Is.EqualTo([firstParameter, secondParameter])); + } } } } diff --git a/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs index 491b6cee..502b2b8b 100644 --- a/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs @@ -24,6 +24,7 @@ namespace SysML2.NET.Tests.Extend using NUnit.Framework; + using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Connectors; @@ -308,19 +309,69 @@ public void VerifyComputeTriggerPayloadParameterOperation() { Assert.That(() => ((ITransitionUsage)null).ComputeTriggerPayloadParameterOperation(), Throws.TypeOf()); - var transitionUsage = new TransitionUsage(); - - // No Trigger TFMs → triggerAction is empty → null. - Assert.That(transitionUsage.ComputeTriggerPayloadParameterOperation(), Is.Null); - - // Trigger TFM wired; triggerAction now resolves (ComputeTransitionFeature is implemented). - // The NotSupportedException is now thrown by AcceptActionUsage.payloadParameter → StepExtensions.ComputeParameter, - // which is still a stub. Expand this test when StepExtensions.ComputeParameter is implemented. - var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; - var acceptAction = new AcceptActionUsage(); - transitionUsage.AssignOwnership(triggerTfm, acceptAction); - - Assert.That(() => transitionUsage.ComputeTriggerPayloadParameterOperation(), Throws.TypeOf()); + // OCL: if triggerAction->isEmpty() then null else triggerAction->first().payloadParameter endif + using (Assert.EnterMultipleScope()) + { + // No Trigger TFMs → triggerAction is empty → null. + var emptyTransition = new TransitionUsage(); + + Assert.That(emptyTransition.ComputeTriggerPayloadParameterOperation(), Is.Null); + + // Kind-filter discrimination — only an Effect-kind TFM (no Trigger) → triggerAction is empty → null. + var effectOnlyTransition = new TransitionUsage(); + var effectTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Effect }; + var effectAccept = new AcceptActionUsage(); + effectOnlyTransition.AssignOwnership(effectTfm, effectAccept); + + Assert.That(effectOnlyTransition.ComputeTriggerPayloadParameterOperation(), Is.Null); + + // Type-filter discrimination — a Trigger TFM whose transitionFeature is a plain ActionUsage + // (not an IAcceptActionUsage) → selectByKind(AcceptActionUsage) excludes it → triggerAction + // is empty → null. + var wrongTypeTransition = new TransitionUsage(); + var wrongTypeTriggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var plainActionUsage = new ActionUsage(); + wrongTypeTransition.AssignOwnership(wrongTypeTriggerTfm, plainActionUsage); + + Assert.That(wrongTypeTransition.ComputeTriggerPayloadParameterOperation(), Is.Null); + + // Trigger TFM present but the AcceptActionUsage has no directed parameter → its payloadParameter + // is null → result is null. + var noPayloadTransition = new TransitionUsage(); + var noPayloadTriggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var bareAccept = new AcceptActionUsage(); + noPayloadTransition.AssignOwnership(noPayloadTriggerTfm, bareAccept); + + Assert.That(noPayloadTransition.ComputeTriggerPayloadParameterOperation(), Is.Null); + + // POSITIVE — a Trigger TFM whose transitionFeature is an AcceptActionUsage that owns a directed + // ReferenceUsage parameter → triggerAction->first().payloadParameter is that ReferenceUsage. + var payloadTransition = new TransitionUsage(); + var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var acceptAction = new AcceptActionUsage(); + var payloadParameter = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + acceptAction.AssignOwnership(new FeatureMembership(), payloadParameter); + payloadTransition.AssignOwnership(triggerTfm, acceptAction); + + Assert.That(payloadTransition.ComputeTriggerPayloadParameterOperation(), Is.SameAs(payloadParameter)); + + // Ordering — two Trigger TFMs each with a distinct payload → triggerAction->first() picks the + // first AcceptActionUsage → its payloadParameter is returned. + var twoTriggerTransition = new TransitionUsage(); + var firstTriggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var firstAccept = new AcceptActionUsage(); + var firstPayload = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + firstAccept.AssignOwnership(new FeatureMembership(), firstPayload); + twoTriggerTransition.AssignOwnership(firstTriggerTfm, firstAccept); + + var secondTriggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var secondAccept = new AcceptActionUsage(); + var secondPayload = new ReferenceUsage { Direction = FeatureDirectionKind.In }; + secondAccept.AssignOwnership(new FeatureMembership(), secondPayload); + twoTriggerTransition.AssignOwnership(secondTriggerTfm, secondAccept); + + Assert.That(twoTriggerTransition.ComputeTriggerPayloadParameterOperation(), Is.SameAs(firstPayload)); + } } [Test] diff --git a/SysML2.NET/Extend/FunctionExtensions.cs b/SysML2.NET/Extend/FunctionExtensions.cs index fbfc150c..871651d4 100644 --- a/SysML2.NET/Extend/FunctionExtensions.cs +++ b/SysML2.NET/Extend/FunctionExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Kernel.Functions { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -48,10 +49,11 @@ internal static class FunctionExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeExpression(this IFunction functionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return functionSubject == null + ? throw new ArgumentNullException(nameof(functionSubject)) + : [.. functionSubject.feature.OfType()]; } /// @@ -91,10 +93,19 @@ internal static bool ComputeIsModelLevelEvaluable(this IFunction functionSubject /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeResult(this IFunction functionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (functionSubject == null) + { + throw new ArgumentNullException(nameof(functionSubject)); + } + + var resultParams = functionSubject.featureMembership + .OfType() + .Select(returnParameterMembership => returnParameterMembership.ownedMemberParameter) + .ToList(); + + return resultParams.Count == 0 ? null : resultParams[0]; } } diff --git a/SysML2.NET/Extend/StepExtensions.cs b/SysML2.NET/Extend/StepExtensions.cs index 6068bd18..121f3631 100644 --- a/SysML2.NET/Extend/StepExtensions.cs +++ b/SysML2.NET/Extend/StepExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Kernel.Behaviors { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -52,10 +53,11 @@ internal static class StepExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeBehavior(this IStep stepSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return stepSubject == null + ? throw new ArgumentNullException(nameof(stepSubject)) + : [.. stepSubject.type.OfType()]; } /// @@ -67,10 +69,11 @@ internal static List ComputeBehavior(this IStep stepSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeParameter(this IStep stepSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return stepSubject == null + ? throw new ArgumentNullException(nameof(stepSubject)) + : [.. TypeExtensions.ComputeDirectedFeature(stepSubject)]; } }