From 988a710a38f2581868d2259f93944fd06d918744 Mon Sep 17 00:00:00 2001 From: atheate Date: Tue, 19 May 2026 07:53:22 +0200 Subject: [PATCH] Fix #191 --- .../AcceptActionUsageExtensionsTestFixture.cs | 39 ++- .../TransitionUsageExtensionsTestFixture.cs | 224 ++++++++++++++++-- .../Extend/TransitionUsageExtensions.cs | 101 ++++++-- 3 files changed, 314 insertions(+), 50 deletions(-) diff --git a/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs index 73e4dc11..b617420b 100644 --- a/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AcceptActionUsageExtensionsTestFixture.cs @@ -29,9 +29,11 @@ namespace SysML2.NET.Tests.Extend using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Expressions; using SysML2.NET.Core.POCO.Kernel.FeatureValues; + using SysML2.NET.Core.POCO.Root.Namespaces; using SysML2.NET.Core.POCO.Systems.Actions; using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; using SysML2.NET.Core.POCO.Systems.States; + using SysML2.NET.Core.Systems.States; using SysML2.NET.Extensions; [TestFixture] @@ -162,14 +164,35 @@ public void VerifyComputeIsTriggerActionOperation() Assert.That(acceptNotTransition.ComputeIsTriggerActionOperation(), Is.False); - // Branch C — owningType IS ITransitionUsage: triggerAction dispatch hits the ComputeTriggerAction stub. - var transitionOwner = new TransitionUsage(); - var featureMembershipC = new FeatureMembership(); - var acceptInTransition = new AcceptActionUsage(); - transitionOwner.AssignOwnership(featureMembershipC, acceptInTransition); - - // For Later: populated case depends on TransitionUsageExtensions.ComputeTriggerAction at SysML2.NET/Extend/TransitionUsageExtensions.cs:208, which is still a stub. - Assert.That(() => acceptInTransition.ComputeIsTriggerActionOperation(), Throws.TypeOf()); + // Branch C1 — owningType IS TransitionUsage, NO Trigger TFM present. + // AcceptActionUsage is owned via plain OwningMembership → ownedFeatureMembership has no TFMs at all. + // triggerAction evaluates to an empty list → .Contains(self) is false. + var transitionOwnerC1 = new TransitionUsage(); + var acceptInTransitionC1 = new AcceptActionUsage(); + transitionOwnerC1.AssignOwnership(new OwningMembership(), acceptInTransitionC1); + + Assert.That(acceptInTransitionC1.ComputeIsTriggerActionOperation(), Is.False); + + // Branch C2 — owningType IS TransitionUsage, with Trigger TFM. + // The Where(Kind == Trigger) filter passes, then .transitionFeature is accessed, which dispatches + // through TransitionFeatureMembershipExtensions.ComputeTransitionFeature (still a stub). + // For Later: depends on TransitionFeatureMembershipExtensions.ComputeTransitionFeature at SysML2.NET/Extend/TransitionFeatureMembershipExtensions.cs:51, which is still a stub. + var transitionOwnerC2 = new TransitionUsage(); + var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var acceptInTransitionC2 = new AcceptActionUsage(); + transitionOwnerC2.AssignOwnership(triggerTfm, acceptInTransitionC2); + + Assert.That(() => acceptInTransitionC2.ComputeIsTriggerActionOperation(), Throws.TypeOf()); + + // Branch C3 — owningType IS TransitionUsage, only non-Trigger TFMs (Effect kind). + // The Where(Kind == Trigger) filter excludes the Effect TFM → triggerAction returns empty list. + // .transitionFeature is never accessed, so the stub is never hit → .Contains(self) is false. + var transitionOwnerC3 = new TransitionUsage(); + var effectTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Effect }; + var acceptInTransitionC3 = new AcceptActionUsage(); + transitionOwnerC3.AssignOwnership(effectTfm, acceptInTransitionC3); + + Assert.That(acceptInTransitionC3.ComputeIsTriggerActionOperation(), Is.False); } } } diff --git a/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs index 3fa722aa..8b0e1050 100644 --- a/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs @@ -1,68 +1,242 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // 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.Connectors; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; using SysML2.NET.Core.POCO.Systems.States; + using SysML2.NET.Core.Systems.States; + using SysML2.NET.Extensions; [TestFixture] public class TransitionUsageExtensionsTestFixture { [Test] - public void ComputeEffectAction_ThrowsNotSupportedException() + public void VerifyComputeEffectAction() { - Assert.That(() => ((ITransitionUsage)null).ComputeEffectAction(), Throws.TypeOf()); + Assert.That(() => ((ITransitionUsage)null).ComputeEffectAction(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // Empty ownedFeatureMembership → empty list. + Assert.That(transitionUsage.ComputeEffectAction(), Has.Count.EqualTo(0)); + + // Only a Trigger-kind TFM present → Effect filter excludes it → still empty. + var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var triggerAction = new AcceptActionUsage(); + transitionUsage.AssignOwnership(triggerTfm, triggerAction); + + Assert.That(transitionUsage.ComputeEffectAction(), Has.Count.EqualTo(0)); + + // Effect-kind TFM wired with an ActionUsage as transitionFeature. + // For Later: populated path depends on TransitionFeatureMembershipExtensions.ComputeTransitionFeature + // at SysML2.NET/Extend/TransitionFeatureMembershipExtensions.cs:51, which is still a stub. + var effectTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Effect }; + var effectAction = new ActionUsage(); + transitionUsage.AssignOwnership(effectTfm, effectAction); + + Assert.That(() => transitionUsage.ComputeEffectAction(), Throws.TypeOf()); } - + [Test] - public void ComputeGuardExpression_ThrowsNotSupportedException() + public void VerifyComputeGuardExpression() { - Assert.That(() => ((ITransitionUsage)null).ComputeGuardExpression(), Throws.TypeOf()); + Assert.That(() => ((ITransitionUsage)null).ComputeGuardExpression(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // Empty ownedFeatureMembership → empty list. + Assert.That(transitionUsage.ComputeGuardExpression(), Has.Count.EqualTo(0)); + + // Only Trigger-kind TFMs → Guard filter excludes them → empty. + var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var triggerAction = new AcceptActionUsage(); + transitionUsage.AssignOwnership(triggerTfm, triggerAction); + + Assert.That(transitionUsage.ComputeGuardExpression(), Has.Count.EqualTo(0)); + + // Guard-kind TFM wired with a LiteralBoolean (IExpression) as transitionFeature. + // For Later: populated path depends on TransitionFeatureMembershipExtensions.ComputeTransitionFeature + // at SysML2.NET/Extend/TransitionFeatureMembershipExtensions.cs:51, which is still a stub. + var guardTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Guard }; + var guardExpression = new LiteralBoolean(); + transitionUsage.AssignOwnership(guardTfm, guardExpression); + + Assert.That(() => transitionUsage.ComputeGuardExpression(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeSource() + { + Assert.That(() => ((ITransitionUsage)null).ComputeSource(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // No qualifying non-FeatureMembership Feature with ActionUsage featureTarget → SourceFeature() returns null → null. + Assert.That(transitionUsage.ComputeSource(), Is.Null); + + // Non-FeatureMembership (OwningMembership) owning an ActionUsage: featureTarget = the ActionUsage itself + // (no chainingFeature) which IS IActionUsage → returned. + var sourceAction = new ActionUsage(); + transitionUsage.AssignOwnership(new OwningMembership(), sourceAction); + + Assert.That(transitionUsage.ComputeSource(), Is.SameAs(sourceAction)); + } + + [Test] + public void VerifyComputeSuccession() + { + Assert.That(() => ((ITransitionUsage)null).ComputeSuccession(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // Empty ownedMember → null. + Assert.That(transitionUsage.ComputeSuccession(), Is.Null); + + // Non-Succession member only → still null. + var nonSuccessionMember = new ActionUsage(); + transitionUsage.AssignOwnership(new OwningMembership(), nonSuccessionMember); + + Assert.That(transitionUsage.ComputeSuccession(), Is.Null); + + // First Succession added → returned. + var firstSuccession = new Succession(); + transitionUsage.AssignOwnership(new OwningMembership(), firstSuccession); + + Assert.That(transitionUsage.ComputeSuccession(), Is.SameAs(firstSuccession)); + + // Second Succession added → first is still returned (OCL ->at(1)). + var secondSuccession = new Succession(); + transitionUsage.AssignOwnership(new OwningMembership(), secondSuccession); + + Assert.That(transitionUsage.ComputeSuccession(), Is.SameAs(firstSuccession)); } - + [Test] - public void ComputeSource_ThrowsNotSupportedException() + public void VerifyComputeTarget() { - Assert.That(() => ((ITransitionUsage)null).ComputeSource(), Throws.TypeOf()); + Assert.That(() => ((ITransitionUsage)null).ComputeTarget(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // No Succession in ownedMember → succession is null → null. + Assert.That(transitionUsage.ComputeTarget(), Is.Null); + + // Succession present but targetFeature access hits ConnectorExtensions.ComputeTargetFeature stub. + // For Later: populated path depends on ConnectorExtensions.ComputeTargetFeature + // at SysML2.NET/Extend/ConnectorExtensions.cs:171, which is still a stub. + var succession = new Succession(); + transitionUsage.AssignOwnership(new OwningMembership(), succession); + + Assert.That(() => transitionUsage.ComputeTarget(), Throws.TypeOf()); } - + [Test] - public void ComputeSuccession_ThrowsNotSupportedException() + public void VerifyComputeTriggerAction() { - Assert.That(() => ((ITransitionUsage)null).ComputeSuccession(), Throws.TypeOf()); + Assert.That(() => ((ITransitionUsage)null).ComputeTriggerAction(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // Empty ownedFeatureMembership → empty list. + Assert.That(transitionUsage.ComputeTriggerAction(), Has.Count.EqualTo(0)); + + // Only Effect-kind and Guard-kind TFMs → Trigger filter excludes them → empty. + var effectTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Effect }; + var effectAction = new ActionUsage(); + transitionUsage.AssignOwnership(effectTfm, effectAction); + + var guardTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Guard }; + var guardExpression = new LiteralBoolean(); + transitionUsage.AssignOwnership(guardTfm, guardExpression); + + Assert.That(transitionUsage.ComputeTriggerAction(), Has.Count.EqualTo(0)); + + // Trigger-kind TFM wired with an AcceptActionUsage as transitionFeature. + // For Later: populated path depends on TransitionFeatureMembershipExtensions.ComputeTransitionFeature + // at SysML2.NET/Extend/TransitionFeatureMembershipExtensions.cs:51, which is still a stub. + var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var acceptAction = new AcceptActionUsage(); + transitionUsage.AssignOwnership(triggerTfm, acceptAction); + + Assert.That(() => transitionUsage.ComputeTriggerAction(), Throws.TypeOf()); } - + [Test] - public void ComputeTarget_ThrowsNotSupportedException() + public void VerifyComputeTriggerPayloadParameterOperation() { - Assert.That(() => ((ITransitionUsage)null).ComputeTarget(), Throws.TypeOf()); + 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 but access to transitionFeature hits ComputeTransitionFeature stub. + // For Later: populated path depends on TransitionFeatureMembershipExtensions.ComputeTransitionFeature + // at SysML2.NET/Extend/TransitionFeatureMembershipExtensions.cs:51, which is still a stub. + var triggerTfm = new TransitionFeatureMembership { Kind = TransitionFeatureKind.Trigger }; + var acceptAction = new AcceptActionUsage(); + transitionUsage.AssignOwnership(triggerTfm, acceptAction); + + Assert.That(() => transitionUsage.ComputeTriggerPayloadParameterOperation(), Throws.TypeOf()); } - + [Test] - public void ComputeTriggerAction_ThrowsNotSupportedException() + public void VerifyComputeSourceFeatureOperation() { - Assert.That(() => ((ITransitionUsage)null).ComputeTriggerAction(), Throws.TypeOf()); + Assert.That(() => ((ITransitionUsage)null).ComputeSourceFeatureOperation(), Throws.TypeOf()); + + var transitionUsage = new TransitionUsage(); + + // Empty ownedMembership → null. + Assert.That(transitionUsage.ComputeSourceFeatureOperation(), Is.Null); + + // All entries are IFeatureMembership → rejected by the non-FeatureMembership filter → null. + var featureMembership = new FeatureMembership(); + var featureViaFm = new ActionUsage(); + transitionUsage.AssignOwnership(featureMembership, featureViaFm); + + Assert.That(transitionUsage.ComputeSourceFeatureOperation(), Is.Null); + + // Non-FeatureMembership (OwningMembership) owning a plain Feature (featureTarget = itself = not IActionUsage) → filtered out → null. + var plainFeature = new Feature(); + transitionUsage.AssignOwnership(new OwningMembership(), plainFeature); + + Assert.That(transitionUsage.ComputeSourceFeatureOperation(), Is.Null); + + // Non-FeatureMembership owning an ActionUsage (featureTarget = itself = IActionUsage) → returned. + var sourceAction = new ActionUsage(); + transitionUsage.AssignOwnership(new OwningMembership(), sourceAction); + + Assert.That(transitionUsage.ComputeSourceFeatureOperation(), Is.SameAs(sourceAction)); } } } diff --git a/SysML2.NET/Extend/TransitionUsageExtensions.cs b/SysML2.NET/Extend/TransitionUsageExtensions.cs index c4064943..e5a3ae4f 100644 --- a/SysML2.NET/Extend/TransitionUsageExtensions.cs +++ b/SysML2.NET/Extend/TransitionUsageExtensions.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // // // Copyright (C) 2022-2026 Starion Group S.A. @@ -22,10 +22,12 @@ namespace SysML2.NET.Core.POCO.Systems.States { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.Systems.Occurrences; + using SysML2.NET.Core.Systems.States; using SysML2.NET.Core.POCO.Core.Classifiers; using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; @@ -82,10 +84,18 @@ internal static class TransitionUsageExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeEffectAction(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + return [..transitionUsageSubject.ownedFeatureMembership + .OfType() + .Where(tfm => tfm.Kind == TransitionFeatureKind.Effect) + .Select(tfm => tfm.transitionFeature) + .OfType()]; } /// @@ -106,10 +116,18 @@ internal static List ComputeEffectAction(this ITransitionUsage tra /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeGuardExpression(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + return [..transitionUsageSubject.ownedFeatureMembership + .OfType() + .Where(tfm => tfm.Kind == TransitionFeatureKind.Guard) + .Select(tfm => tfm.transitionFeature) + .OfType()]; } /// @@ -130,10 +148,16 @@ internal static List ComputeGuardExpression(this ITransitionUsage t /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IActionUsage ComputeSource(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + var sourceFeature = transitionUsageSubject.SourceFeature(); + + return sourceFeature?.featureTarget as IActionUsage; } /// @@ -151,10 +175,14 @@ internal static IActionUsage ComputeSource(this ITransitionUsage transitionUsage /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static ISuccession ComputeSuccession(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + return transitionUsageSubject.ownedMember.OfType().FirstOrDefault(); } /// @@ -180,10 +208,25 @@ internal static ISuccession ComputeSuccession(this ITransitionUsage transitionUs /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IActionUsage ComputeTarget(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + var succession = transitionUsageSubject.succession; + + if (succession == null) + { + return null; + } + + var firstTargetFeature = succession.targetFeature.Count == 0 + ? null + : succession.targetFeature[0]; + + return firstTargetFeature?.featureTarget as IActionUsage; } /// @@ -204,10 +247,18 @@ internal static IActionUsage ComputeTarget(this ITransitionUsage transitionUsage /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeTriggerAction(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + return [..transitionUsageSubject.ownedFeatureMembership + .OfType() + .Where(tfm => tfm.Kind == TransitionFeatureKind.Trigger) + .Select(tfm => tfm.transitionFeature) + .OfType()]; } /// @@ -227,10 +278,18 @@ internal static List ComputeTriggerAction(this ITransitionUs /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IReferenceUsage ComputeTriggerPayloadParameterOperation(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + var triggerActions = transitionUsageSubject.triggerAction; + + return triggerActions.Count == 0 + ? null + : triggerActions[0].payloadParameter; } /// @@ -256,10 +315,18 @@ internal static IReferenceUsage ComputeTriggerPayloadParameterOperation(this ITr /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeSourceFeatureOperation(this ITransitionUsage transitionUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (transitionUsageSubject == null) + { + throw new ArgumentNullException(nameof(transitionUsageSubject)); + } + + return transitionUsageSubject.ownedMembership + .Where(m => m is not IFeatureMembership) + .Select(m => m.MemberElement) + .OfType() + .FirstOrDefault(f => f.featureTarget is IActionUsage); } } }