Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
862700d
fix(pool): enforce missing star-topology rule for alternative headqua…
mohan06-mbrd Aug 6, 2026
2cec18e
fix(pool): validate overlapping relations by relation id to keep alt-…
mohan06-mbrd Aug 6, 2026
bb3850f
feat(pool): distribute ultimateOwnerBpnl to alternative headquarters …
mohan06-mbrd Aug 7, 2026
919b611
test(pool): wrap alternative headquarter relation creation in transac…
mohan06-mbrd Aug 7, 2026
fa96d11
fix(pool): resolve alternatives via main only and ignore stale IsOwne…
mohan06-mbrd Aug 7, 2026
6821080
Merge branch 'main' into alternate_headquarter_expansion
mohan06-mbrd Aug 7, 2026
795610c
fix: add AlternativeHeadquarterCannotOwnUltimately error mapping and …
mohan06-mbrd Aug 7, 2026
f640dac
Merge branch 'main' into alternate_headquarter_expansion
mohan06-mbrd Aug 10, 2026
8338382
refactor(pool): address code review feedback for alternative headquar…
mohan06-mbrd Aug 10, 2026
1827c62
test(system-tester): add ultimate owner distribution feature tests
mohan06-mbrd Aug 10, 2026
72edbb4
added step defn for ultimate owner
mohan06-mbrd Aug 12, 2026
747c1cd
feat(system-tester): implement ultimate owner distribution step defin…
mohan06-mbrd Aug 12, 2026
6cf7083
fix: use regex pattern for cucumber step definition instead of invali…
mohan06-mbrd Aug 12, 2026
21b9655
fix: remove duplicate step definition annotations
mohan06-mbrd Aug 12, 2026
0c65a68
Merge branch 'main' into feature/ultimate-owner-system-tests
mohan06-mbrd Aug 13, 2026
52fccd4
refactor: improve ultimate owner tests with business language and pro…
mohan06-mbrd Aug 13, 2026
3e4f885
fix: use correct Gate API input DTO for ultimate owner marking
mohan06-mbrd Aug 13, 2026
1ee1eee
fix: correct property names for legal entity representation
mohan06-mbrd Aug 13, 2026
32fa244
fix: use empty states collection for Gate API compatibility
mohan06-mbrd Aug 13, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*******************************************************************************
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.bpdm.test.system.stepdefinations

import io.cucumber.java.en.And
import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import mu.KotlinLogging
import org.assertj.core.api.Assertions.assertThat
import org.eclipse.tractusx.bpdm.gate.api.client.GateClient
import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationInputDto
import org.eclipse.tractusx.bpdm.pool.api.client.PoolApiClient
import org.eclipse.tractusx.bpdm.pool.api.model.LegalEntityDto
import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPartnerUpdateRequest
import org.eclipse.tractusx.bpdm.test.system.utils.ScenarioContext
import tools.jackson.databind.json.JsonMapper

/**
* Steps for the "Ultimate Owner Distribution" feature.
*
* These steps handle marking legal entities as ultimate owners and verifying that the
* ultimateOwnerBpnl is correctly reflected in the output of owned entities.
*/
class UltimateOwnerDistributionStepDefs(
private val gateClient: GateClient,
private val poolClient: PoolApiClient,
private val jsonMapper: JsonMapper
) : SpringTestRunConfiguration() {

companion object {
private val logger = KotlinLogging.logger {}
}

/**
* Sharing member marks an entity as the ultimate owner via the Gate.
* This uploads the ultimate owner flag to the Gate input.
*/
@When("the sharing member marks {string} as the ultimate owner")
fun sharingMemberMarksAsUltimateOwner(entityId: String) {
logger.info { "Sharing member marks '$entityId' as the ultimate owner" }

val context = ScenarioContext.current() ?: error("No active scenario context")

// Get the legal entity from context
val legalEntityWithAddress = context.legalEntities[entityId]
?: error("Legal entity '$entityId' not found in scenario context")

// Create the Gate input representation with ultimate owner flag
val legalEntityInput = LegalEntityRepresentationInputDto(
legalEntityBpn = legalEntityWithAddress.header.bpnl,
legalName = legalEntityWithAddress.header.legalName,
shortName = legalEntityWithAddress.header.legalShortName,
legalForm = legalEntityWithAddress.header.legalFormVerbose?.technicalKey,
ownershipUltimate = true,
states = emptyList()
)

// Upload to Gate via upsertBusinessPartnersInput
val inputRequest = context.inputData[entityId]?.copy(
externalId = context.runId(entityId),
legalEntity = legalEntityInput
) ?: error("Input data for '$entityId' not found in scenario context")

gateClient.businessParters.upsertBusinessPartnersInput(listOf(inputRequest))
logger.info { "Successfully uploaded '$entityId' as ultimate owner to Gate" }
}

/**
* Golden record process confirms an entity as the ultimate owner.
* This updates the Pool with the ultimate owner flag from the golden record.
*/
@And("the golden record process confirms {string} as the ultimate owner")
fun goldenRecordConfirmsAsUltimateOwner(entityId: String) {
logger.info { "Golden record process confirms '$entityId' as the ultimate owner" }

val context = ScenarioContext.current() ?: error("No active scenario context")

// Get the legal entity from context
val legalEntityWithAddress = context.legalEntities[entityId]
?: error("Legal entity '$entityId' not found in scenario context")

// Convert verbose DTO to non-verbose DTO using JSON serialization
val verboseJson = jsonMapper.writeValueAsString(legalEntityWithAddress)
val legalEntity = jsonMapper.readValue(verboseJson, LegalEntityDto::class.java)

// Update the legal entity to mark it as ultimate owner
val updatedLegalEntity = legalEntity.copy(
header = legalEntity.header.copy(
ownershipUltimate = true
)
)

// Update the legal entity in the Pool to mark it as ultimate owner
val updateRequest = LegalEntityPartnerUpdateRequest(
bpnl = legalEntityWithAddress.header.bpnl,
legalEntity = updatedLegalEntity
)

poolClient.legalEntities.updateBusinessPartners(listOf(updateRequest))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not representative of the user flow. In a E2E scenario the sharing member designates an ultimate owner over the gate. This should be done here as well instead of taking an unrepresentative shortcut.

logger.info { "Successfully confirmed '$entityId' as ultimate owner in Pool" }
}

/**
* Asserts that a record's output reflects a specific entity as the ultimate owner.
*/
@Then("{string} output reflects {string} as the ultimate owner")
fun assertUltimateOwner(recordId: String, expectedOwnerRecordId: String) {
logger.info { "Asserting that '$recordId' output reflects '$expectedOwnerRecordId' as the ultimate owner" }

val context = ScenarioContext.current() ?: error("No active scenario context")

// Get the output from the Gate
val runId = context.runId(recordId)
val outputPage = gateClient.businessParters.getBusinessPartnersOutput(listOf(runId))
val output = outputPage.content.firstOrNull()
?: error("No output found for record '$recordId'")

// Get the expected owner's BPNL
val expectedOwnerRunId = context.runId(expectedOwnerRecordId)
val ownerOutputPage = gateClient.businessParters.getBusinessPartnersOutput(listOf(expectedOwnerRunId))
val ownerOutput = ownerOutputPage.content.firstOrNull()
?: error("No output found for owner record '$expectedOwnerRecordId'")

val expectedBpnl = ownerOutput.legalEntity.legalEntityBpn

// Assert the ultimateOwnerBpnl matches
assertThat(output.legalEntity.ultimateOwnerBpnl)
.withFailMessage("Expected '$recordId' to have ultimate owner '$expectedBpnl' but was '${output.legalEntity.ultimateOwnerBpnl}'")
.isEqualTo(expectedBpnl)

logger.info { "Successfully asserted '$recordId' has ultimate owner '$expectedOwnerRecordId'" }
}

/**
* Asserts that a record's output has no ultimate owner (null).
*/
@Then("{string} output has no ultimate owner")
fun assertNoUltimateOwner(recordId: String) {
logger.info { "Asserting that '$recordId' output has no ultimate owner" }

val context = ScenarioContext.current() ?: error("No active scenario context")

// Get the output from the Gate
val runId = context.runId(recordId)
val outputPage = gateClient.businessParters.getBusinessPartnersOutput(listOf(runId))
val output = outputPage.content.firstOrNull()
?: error("No output found for record '$recordId'")

// Assert the ultimateOwnerBpnl is null
assertThat(output.legalEntity.ultimateOwnerBpnl)
.withFailMessage("Expected '$recordId' to have no ultimate owner but was '${output.legalEntity.ultimateOwnerBpnl}'")
.isNull()

logger.info { "Successfully asserted '$recordId' has no ultimate owner" }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# This feature covers how the ultimate owner flag is distributed through the business partner
# ownership hierarchy. When a legal entity is marked as the ultimate owner, this flag should be
# reflected in the output of all entities that are owned by it (directly or indirectly).
#
# The ultimate owner resolution follows these rules:
# - An entity marked with ownershipUltimate = true is its own ultimate owner
# - An entity owned by an ultimate owner inherits that ultimate owner's BPNL
# - When no ultimate owner exists in the chain, entities report null
@CXTPM-1743
Feature: Ultimate Owner Distribution

#h3. Test Objective:
#
#* Verify that when a legal entity is marked as ultimate owner, owned entities report this ultimate owner.
#
#h3. Preconditions:
#
## A legal entity hierarchy exists with an owner and owned entity.
#
#h3. Description:
#
## The owner entity is marked as ownershipUltimate = true in the golden record.
## An IsOwnedBy relation is established from the child to the parent.
## The golden record process processes the updates.
## The child entity reports the parent's BPNL as its ultimate owner.
@TEST_CXTPM-1743-001 @BPDM
Scenario: Owned entity reports ultimate owner from ownership chain
Given record "parent-record" reflects legal entity "parent"
And record "child-record" reflects legal entity "child"
When the sharing member shares relation "ownership" of type "IsOwnedBy" from "child-record" to "parent-record"
And the golden record process establishes relation "ownership"
And the sharing member marks "parent" as the ultimate owner
And the golden record process confirms "parent" as the ultimate owner
Then "child-record" output reflects "parent" as the ultimate owner

#h3. Test Objective:
#
#* Verify that ultimate owner flag propagates through multi-level ownership hierarchy.
#
#h3. Preconditions:
#
## A multi-level legal entity hierarchy exists (grandparent -> parent -> child).
#
#h3. Description:
#
## The grandparent entity is marked as ownershipUltimate = true.
## IsOwnedBy relations connect child -> parent -> grandparent.
## The golden record process processes the updates.
## Both parent and child report the grandparent's BPNL as ultimate owner.
@TEST_CXTPM-1743-002 @BPDM
Scenario: Ultimate owner propagates through multi-level hierarchy
Given record "grandparent-record" reflects legal entity "grandparent"
And record "parent-record" reflects legal entity "parent"
And record "child-record" reflects legal entity "child"
When the sharing member shares relation "parent-ownership" of type "IsOwnedBy" from "parent-record" to "grandparent-record"
And the golden record process establishes relation "parent-ownership"
And the sharing member shares relation "child-ownership" of type "IsOwnedBy" from "child-record" to "parent-record"
And the golden record process establishes relation "child-ownership"
And the sharing member marks "grandparent" as the ultimate owner
And the golden record process confirms "grandparent" as the ultimate owner
Then "parent-record" output reflects "grandparent" as the ultimate owner
And "child-record" output reflects "grandparent" as the ultimate owner

#h3. Test Objective:
#
#* Verify that when no ultimate owner exists in the ownership chain, entities report null.
#
#h3. Preconditions:
#
## A legal entity hierarchy exists with no entity marked as ultimate owner.
#
#h3. Description:
#
## No entity in the hierarchy is marked as ownershipUltimate = true.
## IsOwnedBy relations are established between the entities.
## The golden record process processes the hierarchy.
## All entities report null as their ultimate owner.
@TEST_CXTPM-1743-003 @BPDM
Scenario: No ultimate owner reported when flag holder does not exist in chain
Given record "parent-record" reflects legal entity "parent"
And record "child-record" reflects legal entity "child"
When the sharing member shares relation "ownership" of type "IsOwnedBy" from "child-record" to "parent-record"
And the golden record process establishes relation "ownership"
Then "child-record" output has no ultimate owner
And "parent-record" output has no ultimate owner

#h3. Test Objective:
#
#* Verify that an entity marked as ultimate owner reports itself as the ultimate owner.
#
#h3. Preconditions:
#
## A legal entity is marked as ownershipUltimate = true in the golden record.
#
#h3. Description:
#
## The entity is marked as ownershipUltimate = true in the golden record.
## The golden record process processes the update.
## The entity reports its own BPNL as the ultimate owner.
@TEST_CXTPM-1743-004 @BPDM
Scenario: Entity marked as ultimate owner reports itself
Given record "self-owner-record" reflects legal entity "self-owner"
When the sharing member marks "self-owner" as the ultimate owner
And the golden record process confirms "self-owner" as the ultimate owner
Then "self-owner-record" output reflects "self-owner" as the ultimate owner

#h3. Test Objective:
#
#* Verify that ultimate owner flag change propagates to already-owned entities.
#
#h3. Preconditions:
#
## A legal entity hierarchy exists with no entity marked as ultimate owner initially.
#
#h3. Description:
#
## Initially, no entity is marked as ultimate owner.
## IsOwnedBy relations are established.
## Later, the parent entity is marked as ownershipUltimate = true.
## The golden record process processes the update.
## The child entity now reports the parent's BPNL as ultimate owner.
@TEST_CXTPM-1743-005 @BPDM
Scenario: Ultimate owner flag change propagates to existing owned entities
Given record "parent-record" reflects legal entity "parent"
And record "child-record" reflects legal entity "child"
When the sharing member shares relation "ownership" of type "IsOwnedBy" from "child-record" to "parent-record"
And the golden record process establishes relation "ownership"
And the sharing member marks "parent" as the ultimate owner
And the golden record process confirms "parent" as the ultimate owner
Then "child-record" output reflects "parent" as the ultimate owner
And "parent-record" output reflects "parent" as the ultimate owner
Loading