Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 92 additions & 12 deletions SysML2.NET.Tests/Extend/ImportExtensionsTestFixture.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,124 @@
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// <copyright file="ImportExtensionsTestFixture.cs" company="Starion Group S.A.">
//
//
// 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.
//
//
// </copyright>
// ------------------------------------------------------------------------------------------------

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<NotSupportedException>());
Assert.That(() => ((IImport)null).ComputeImportedElement(), Throws.TypeOf<ArgumentNullException>());

// 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<IImport>().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<IMembership>();
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<ArgumentNullException>());

// 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<NotSupportedException>());

Assert.That(
() => new NamespaceImport().ComputeImportedMembershipsOperation([]),
Throws.TypeOf<NotSupportedException>());

Assert.That(
() => new MembershipImport().ComputeImportedMembershipsOperation([]),
Throws.TypeOf<NotSupportedException>());
}
}
}
38 changes: 31 additions & 7 deletions SysML2.NET/Extend/ImportExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,33 @@
/// <summary>
/// Computes the derived property.
/// </summary>
/// <remarks>
/// Spec-text-only: the UML <c>Import.importedElement</c> attribute has no OCL body of its
/// own. Each concrete subclass defines its own derivation rule
/// (<c>deriveMembershipImportImportedElement</c>: <c>importedElement = importedMembership.memberElement</c>,
/// and <c>deriveNamespaceImportImportedElement</c>: <c>importedElement = importedNamespace</c>).
/// Both subtype POCOs route their <c>importedElement</c> getter through this single static
/// extension, so the dispatch happens here via a switch on the subject's concrete type.
/// </remarks>
/// <param name="importSubject">
/// The subject <see cref="IImport"/>
/// </param>
/// <returns>
/// the computed result
/// </returns>
[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));
}

Check warning on line 58 in SysML2.NET/Extend/ImportExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'ArgumentNullException.ThrowIfNull' instead of explicitly throwing a new exception instance

See more on https://sonarcloud.io/project/issues?id=STARIONGROUP_SysML2.NET&issues=AZ5jXoQ6zA4mszd2AMpk&open=AZ5jXoQ6zA4mszd2AMpk&pullRequest=260

return importSubject switch
{
IMembershipImport membershipImport => membershipImport.ImportedMembership?.MemberElement,
INamespaceImport namespaceImport => namespaceImport.ImportedNamespace,
_ => null
};
}

/// <summary>
Expand Down Expand Up @@ -76,15 +93,22 @@
/// The expected collection of <see cref="IMembership" />
/// </returns>
/// <remarks>
/// This method is no longer called at runtime. The POCO classes (<see cref="NamespaceImport"/>,
/// <see cref="MembershipImport"/>) now provide explicit interface implementations of
/// <see cref="IImport.ImportedMemberships"/> that dispatch directly to their respective
/// <c>ComputeRedefinedImportedMembershipsOperation</c> extension methods.
/// The UML <c>Import.importedMemberships</c> operation is abstract (no body in the XMI). Both
/// concrete subclasses, <see cref="NamespaceImport"/> and <see cref="MembershipImport"/>, redefine
/// it with their own OCL <c>bodyCondition</c>s, and their POCO partials provide explicit-interface
/// implementations of <see cref="IImport.ImportedMemberships"/> that dispatch directly to their
/// respective <c>ComputeRedefinedImportedMembershipsOperation</c> extension methods. Consequently,
/// this static extension on the abstract base is never reached at runtime, and a deliberate
/// <see cref="NotSupportedException"/> guards any future direct call.
/// </remarks>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static List<IMembership> ComputeImportedMembershipsOperation(this IImport importSubject, List<INamespace> 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.");
}
}
}
Loading