diff --git a/SysML2.NET.Tests/Extend/ImportExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/ImportExtensionsTestFixture.cs index b3e794bb..8b1fceba 100644 --- a/SysML2.NET.Tests/Extend/ImportExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/ImportExtensionsTestFixture.cs @@ -1,44 +1,124 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // 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 Moq; + using NUnit.Framework; - + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; + + using IElement = SysML2.NET.Core.POCO.Root.Elements.IElement; [TestFixture] public class ImportExtensionsTestFixture { [Test] - public void ComputeImportedElement_ThrowsNotSupportedException() + public void VerifyComputeImportedElement() { - Assert.That(() => ((IImport)null).ComputeImportedElement(), Throws.TypeOf()); + Assert.That(() => ((IImport)null).ComputeImportedElement(), Throws.TypeOf()); + + // Wildcard arm: an IImport that is neither an INamespaceImport nor an IMembershipImport + // returns null. Use Moq because IImport is abstract and has no concrete non-subtype POCO. + var abstractImport = new Mock().Object; + + Assert.That(abstractImport.ComputeImportedElement(), Is.Null); + + // INamespaceImport arm: returns the ImportedNamespace. + var importedNamespace = new Namespace(); + var namespaceImport = new NamespaceImport { ImportedNamespace = importedNamespace }; + + Assert.That(namespaceImport.ComputeImportedElement(), Is.SameAs(importedNamespace)); + + // INamespaceImport with no ImportedNamespace set → null. + var emptyNamespaceImport = new NamespaceImport(); + + Assert.That(emptyNamespaceImport.ComputeImportedElement(), Is.Null); + + // IMembershipImport arm: returns the ImportedMembership.MemberElement. + var ownerNamespace = new Namespace(); + var memberElement = new Namespace(); + var importedMembership = new OwningMembership(); + ownerNamespace.AssignOwnership(importedMembership, memberElement); + + var membershipImport = new MembershipImport { ImportedMembership = importedMembership }; + + Assert.That(membershipImport.ComputeImportedElement(), Is.SameAs(memberElement)); + + // IMembershipImport with no ImportedMembership set → null (null-conditional propagates). + var emptyMembershipImport = new MembershipImport(); + + Assert.That(emptyMembershipImport.ComputeImportedElement(), Is.Null); + + // IMembershipImport whose ImportedMembership has a null MemberElement → null. + // A real OwningMembership with zero OwnedRelatedElement throws IncompleteModelException + // from ComputeOwnedMemberElement's invariant guard, so use a Moq'd IMembership to + // produce a null MemberElement directly without violating the model invariant. + var nullMemberMembership = new Mock(); + nullMemberMembership.SetupGet(membership => membership.MemberElement).Returns((IElement)null); + var membershipImportNoMember = new MembershipImport { ImportedMembership = nullMemberMembership.Object }; + + Assert.That(membershipImportNoMember.ComputeImportedElement(), Is.Null); } - + [Test] - public void ComputeImportOwningNamespace_ThrowsArgumentNullException() + public void VerifyComputeImportOwningNamespace() { Assert.That(() => ((IImport)null).ComputeImportOwningNamespace(), Throws.TypeOf()); + + // No owner wired → null. + var orphanImport = new NamespaceImport(); + + Assert.That(orphanImport.ComputeImportOwningNamespace(), Is.Null); + + // Owner wired via AssignOwnership → returns the owning namespace. + var owningNamespace = new Namespace(); + var ownedImport = new NamespaceImport(); + owningNamespace.AssignOwnership(ownedImport); + + Assert.That(ownedImport.ComputeImportOwningNamespace(), Is.SameAs(owningNamespace)); + } + + [Test] + public void VerifyComputeImportedMembershipsOperation() + { + // Import.importedMemberships is abstract in the UML; both concrete subclasses + // (NamespaceImport, MembershipImport) redefine it, and their POCO partials route + // IImport.ImportedMemberships(...) directly to ComputeRedefinedImportedMembershipsOperation + // on the matching subtype. This static extension on the abstract base is therefore + // unreachable at runtime and is documented as a deliberate NotSupportedException guard. + Assert.That( + () => ((IImport)null).ComputeImportedMembershipsOperation([]), + Throws.TypeOf()); + + Assert.That( + () => new NamespaceImport().ComputeImportedMembershipsOperation([]), + Throws.TypeOf()); + + Assert.That( + () => new MembershipImport().ComputeImportedMembershipsOperation([]), + Throws.TypeOf()); } } } diff --git a/SysML2.NET/Extend/ImportExtensions.cs b/SysML2.NET/Extend/ImportExtensions.cs index c75a1829..40a3de45 100644 --- a/SysML2.NET/Extend/ImportExtensions.cs +++ b/SysML2.NET/Extend/ImportExtensions.cs @@ -36,16 +36,33 @@ internal static class ImportExtensions /// /// Computes the derived property. /// + /// + /// Spec-text-only: the UML Import.importedElement attribute has no OCL body of its + /// own. Each concrete subclass defines its own derivation rule + /// (deriveMembershipImportImportedElement: importedElement = importedMembership.memberElement, + /// and deriveNamespaceImportImportedElement: importedElement = importedNamespace). + /// Both subtype POCOs route their importedElement getter through this single static + /// extension, so the dispatch happens here via a switch on the subject's concrete type. + /// /// /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IElement ComputeImportedElement(this IImport importSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (importSubject == null) + { + throw new ArgumentNullException(nameof(importSubject)); + } + + return importSubject switch + { + IMembershipImport membershipImport => membershipImport.ImportedMembership?.MemberElement, + INamespaceImport namespaceImport => namespaceImport.ImportedNamespace, + _ => null + }; } /// @@ -76,15 +93,22 @@ internal static INamespace ComputeImportOwningNamespace(this IImport importSubje /// The expected collection of /// /// - /// This method is no longer called at runtime. The POCO classes (, - /// ) now provide explicit interface implementations of - /// that dispatch directly to their respective - /// ComputeRedefinedImportedMembershipsOperation extension methods. + /// The UML Import.importedMemberships operation is abstract (no body in the XMI). Both + /// concrete subclasses, and , redefine + /// it with their own OCL bodyConditions, and their POCO partials provide explicit-interface + /// implementations of that dispatch directly to their + /// respective ComputeRedefinedImportedMembershipsOperation extension methods. Consequently, + /// this static extension on the abstract base is never reached at runtime, and a deliberate + /// guards any future direct call. /// [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeImportedMembershipsOperation(this IImport importSubject, List excluded) { - throw new NotSupportedException("This method should not be called. Import subtypes handle dispatch via explicit interface implementations."); + throw new NotSupportedException( + "Import is abstract and its importedMemberships operation is redefined by every concrete subclass " + + "(NamespaceImport, MembershipImport). The POCO partials route IImport.ImportedMemberships(...) " + + "directly to ComputeRedefinedImportedMembershipsOperation on the matching subtype, so this static " + + "extension is unreachable at runtime."); } } }