diff --git a/.github/workflows/integration-test-DEV.yaml b/.github/workflows/integration-test-DEV.yaml index 689a647229..8fb3a551c2 100644 --- a/.github/workflows/integration-test-DEV.yaml +++ b/.github/workflows/integration-test-DEV.yaml @@ -23,9 +23,9 @@ jobs: trigger-integration-test: uses: ./.github/workflows/xray-cucumber-integration.yaml secrets: - keycloakTokenUrl: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_TOKEN_URI }} - clientId: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_ID }} - clientSecret: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_SECRET }} + oauth2TokenUrl: ${{ secrets.OAUTH2_CLIENT_TOKEN_URI }} + clientId: ${{ secrets.OAUTH2_CLIENT_ID }} + clientSecret: ${{ secrets.OAUTH2_CLIENT_SECRET }} jiraUser: ${{ secrets.ORG_IRS_JIRA_USERNAME }} jiraPassword: ${{ secrets.ORG_IRS_JIRA_PASSWORD }} with: diff --git a/.github/workflows/integration-test-DIL.yaml b/.github/workflows/integration-test-DIL.yaml index 30edcc77a2..501a816659 100644 --- a/.github/workflows/integration-test-DIL.yaml +++ b/.github/workflows/integration-test-DIL.yaml @@ -12,7 +12,7 @@ jobs: trigger-integration-test: uses: ./.github/workflows/xray-cucumber-integration.yaml secrets: - keycloakTokenUrl: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_TOKEN_URI }} + oauth2TokenUrl: ${{ secrets.OAUTH2_CLIENT_TOKEN_URI }} clientId: ${{ secrets.IRS_OAUTH2_CLIENT_ID_DIL }} clientSecret: ${{ secrets.IRS_OAUTH2_CLIENT_SECRET_DIL }} jiraUser: ${{ secrets.ORG_IRS_JIRA_USERNAME }} diff --git a/.github/workflows/integration-test-INT.yaml b/.github/workflows/integration-test-INT.yaml index 140bd54d00..bbe00c2634 100644 --- a/.github/workflows/integration-test-INT.yaml +++ b/.github/workflows/integration-test-INT.yaml @@ -7,7 +7,7 @@ jobs: trigger-integration-test: uses: ./.github/workflows/xray-cucumber-integration.yaml secrets: - keycloakTokenUrl: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_TOKEN_URI }} + oauth2TokenUrl: ${{ secrets.OAUTH2_CLIENT_TOKEN_URI }} clientId: ${{ secrets.ORG_IRS_OAUTH2_CLIENT_ID_INT }} clientSecret: ${{ secrets.ORG_IRS_OAUTH2_CLIENT_SECRET_INT }} jiraUser: ${{ secrets.ORG_IRS_JIRA_USERNAME }} diff --git a/.github/workflows/xray-cucumber.yaml b/.github/workflows/xray-cucumber.yaml index 633e90f482..3b73158595 100644 --- a/.github/workflows/xray-cucumber.yaml +++ b/.github/workflows/xray-cucumber.yaml @@ -31,9 +31,9 @@ jobs: trigger-integration-test: uses: ./.github/workflows/xray-cucumber-integration.yaml secrets: - keycloakTokenUrl: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_TOKEN_URI }} - clientId: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_ID }} - clientSecret: ${{ secrets.KEYCLOAK_OAUTH2_CLIENT_SECRET }} + oauth2TokenUrl: ${{ secrets.OAUTH2_CLIENT_TOKEN_URI }} + clientId: ${{ secrets.OAUTH2_CLIENT_ID }} + clientSecret: ${{ secrets.OAUTH2_CLIENT_SECRET }} jiraUser: ${{ secrets.ORG_IRS_JIRA_USERNAME }} jiraPassword: ${{ secrets.ORG_IRS_JIRA_PASSWORD }} with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 387fba410b..beb07ef324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- ESS + - Added 'hops' parameter to SupplyChainImpacted Aspect model - contains relative distance in the supply chain + - Added `impactedSuppliersOnFirstTier` parameter to Supply SupplyChainImpacted Aspect model - contains information of first level supply chain impacted + +### Known knowns +- [#253] Cancelation of order jobs is not working stable ## [4.1.0] - 2023-11-15 ### Added diff --git a/charts/irs-helm/CHANGELOG.md b/charts/irs-helm/CHANGELOG.md index 3b49b76f00..a00af20634 100644 --- a/charts/irs-helm/CHANGELOG.md +++ b/charts/irs-helm/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Fixed templating for `management.health.dependencies` ## [6.10.0] ### Changed diff --git a/charts/irs-helm/templates/configmap-spring-app-config.yaml b/charts/irs-helm/templates/configmap-spring-app-config.yaml index 344182606c..fbc0d8b35f 100644 --- a/charts/irs-helm/templates/configmap-spring-app-config.yaml +++ b/charts/irs-helm/templates/configmap-spring-app-config.yaml @@ -135,9 +135,9 @@ data: management: health: dependencies: - enabled: {{ tpl (.Values.management.health.dependencies.enabled | default "false") . | quote }} + enabled: {{ .Values.management.health.dependencies.enabled | default false }} urls: - {{- tpl (toYaml .Values.management.health.dependencies.urls) . | nindent 10 }} + {{- tpl (toYaml .Values.management.health.dependencies.urls) . | nindent 12 }} {{- end }} {{- end }} diff --git a/charts/irs-helm/values.yaml b/charts/irs-helm/values.yaml index d3fc3421ab..2a8c20bb1a 100644 --- a/charts/irs-helm/values.yaml +++ b/charts/irs-helm/values.yaml @@ -113,7 +113,8 @@ management: health: dependencies: enabled: false # Flag to determine if external service healthcheck endpoints should be checked - urls: {} # Map of services with corresponding healthcheck endpoint url's, example service_name: http://service_name_host.com/health + urls: {} # Map of services with corresponding healthcheck endpoint url's. Example: + # service_name: http://service_name_host.com/health digitalTwinRegistry: type: decentral # The type of DTR. This can be either "central" or "decentral". If "decentral", descriptorEndpoint, shellLookupEndpoint and oAuthClientId is not required. diff --git a/docs/src/api/irs-api.yaml b/docs/src/api/irs-api.yaml index 1c57db2903..22d0fbf48b 100644 --- a/docs/src/api/irs-api.yaml +++ b/docs/src/api/irs-api.yaml @@ -2524,6 +2524,11 @@ components: type: object additionalProperties: false properties: + hops: + type: integer + format: int32 + bpn: + type: string result: type: string SemanticId: @@ -2566,10 +2571,10 @@ components: items: $ref: '#/components/schemas/Endpoint' maxItems: 2147483647 - idShort: - type: string id: type: string + idShort: + type: string semanticId: $ref: '#/components/schemas/Reference' Summary: diff --git a/docs/src/docs/arc42/runtime-view/ess-top-down/ess-top-down.adoc b/docs/src/docs/arc42/runtime-view/ess-top-down/ess-top-down.adoc index 5062d1b797..d2ab5e630d 100644 --- a/docs/src/docs/arc42/runtime-view/ess-top-down/ess-top-down.adoc +++ b/docs/src/docs/arc42/runtime-view/ess-top-down/ess-top-down.adoc @@ -94,45 +94,45 @@ In case at least one "YES" is received, the process step 3 ends == Application Functionality Overview -== Register an Ess-Investigation-Order +=== Register an Ess-Investigation-Order [plantuml,target=submodel-processing,format=svg] .... include::../../../../uml-diagrams/runtime-view/use-case-ess-top-down/1_ess-top-down-sequence-highlevel.puml[] .... -== 1. Client Request +==== 1. Client Request The _Client App (Script)_ initiates a request to the IRS (Item Relationship Service) by sending a GET request for shell lookup based on a specific BPN (Business Partner Number). -== 2. Shell Lookup +==== 2. Shell Lookup IRS, along with other services like DiscoveryFinder, EDCDiscoveryService, and Digital Twin Registry, collaborates to look up shells for the given BPN, returning an array of AAS identifiers. -== 3. AAS Descriptor Retrieval +==== 3. AAS Descriptor Retrieval For each AAS identifier, the client requests the IRS for the corresponding shell descriptors, adding them to a collection. -== 4. Filtering AAS +==== 4. Filtering AAS The _Client App (Script)_ filters the AAS collection for SubmodelDescriptors marked _asPlanned_, based on certain criteria. -== 5. Incident Registration +==== 5. Incident Registration The client then initiates an IRS incident registration by sending a POST request with specific parameters, including bomLifecycle and callback URL. -== 6. Incident Handling Loop +==== 6. Incident Handling Loop IRS proceeds to handle the incident by iterating through AAS identifiers, extracting child CXIds, and performing checks on associated data. -== 7. Data Validation +==== 7. Data Validation The system checks the validity period of the received data and, if valid, proceeds to extract additional information. -== 8. Incident Response +==== 8. Incident Response If certain conditions are met (e.g., incidentBpns contain catenaXsiteId), the system responds to the client indicating a part-chain infection. -== 9. Notification Handling +==== 9. Notification Handling Otherwise, the system sends an ess-request notification to the next tier level IRS, and after processing the request on the next tier level, receives an ess-response notification. diff --git a/docs/src/docs/arc42/runtime-view/full.adoc b/docs/src/docs/arc42/runtime-view/full.adoc index 678450e74d..21aa1e6b2b 100644 --- a/docs/src/docs/arc42/runtime-view/full.adoc +++ b/docs/src/docs/arc42/runtime-view/full.adoc @@ -17,7 +17,6 @@ include::irs-iterative/scenario-4.adoc[leveloffset=+1] This section covers the main processes of the IRS in a recursive scenario in a network. This recursive scenario is illustrated using the various use cases realized in the scenario. -=== Use Case: ESS (Environmental and Social Standards) Top-Down approach include::ess-top-down/ess-top-down.adoc[leveloffset=+1] include::ess-top-down/ess-top-down-scenario-1.adoc[leveloffset=+1] diff --git a/docs/src/docs/security/security-assessment.md b/docs/src/docs/security/security-assessment.md index a2bd28e658..cd83d1f730 100644 --- a/docs/src/docs/security/security-assessment.md +++ b/docs/src/docs/security/security-assessment.md @@ -13,7 +13,7 @@ System_Ext(EDC, "EDC") System_Ext(EDC-DS, "EDC Discovery Service") System_Ext(DF, "Discovery Finder") System_Ext(DTR, "Digital Twin Registry") -System_Ext(KC, "Keycloak") +System_Ext(OAuth2, "OAuth2") System_Ext(BPDM, "BPDM") System_Ext(SH, "Semantic Hub") @@ -46,7 +46,7 @@ Rel(IRS-App, EDC, "https, access token") Rel(IRS-App, EDC-DS, "Find decentral DTs, https, access token") Rel(IRS-App, DF, "Get EDC Discovery Service URL, https, access token") Rel(IRS-App, DTR, "https, access token") -Rel(IRS-App, KC, "https, clientID, clientSecret, Get tokens to access DTR") +Rel(IRS-App, OAuth2, "https, clientID, clientSecret, Get tokens to access DTR") Rel(IRS-App, BPDM, "https, access token, Get BPN") Rel(IRS-App, SH, "Get schemas to validate response from EDC, https, access token") diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/BpnInvestigationJob.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/BpnInvestigationJob.java index 718675c882..6ef37b2514 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/BpnInvestigationJob.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/BpnInvestigationJob.java @@ -27,57 +27,44 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Optional; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.component.Job; import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.Notification; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.Summary; import org.eclipse.tractusx.irs.component.enums.JobState; +import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; +import org.eclipse.tractusx.irs.edc.client.model.notification.ResponseNotificationContent; +import org.eclipse.tractusx.irs.util.JsonUtil; /** * Object to store in cache */ @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j public class BpnInvestigationJob { private static final String SUPPLY_CHAIN_ASPECT_TYPE = "supply_chain_impacted"; private Jobs jobSnapshot; private List incidentBpns; - private List unansweredNotifications; - private List answeredNotifications; + private List unansweredNotifications; + private List> answeredNotifications; private JobState state; public BpnInvestigationJob(final Jobs jobSnapshot, final List incidentBpns) { this(jobSnapshot, incidentBpns, new ArrayList<>(), new ArrayList<>(), JobState.RUNNING); } - private static Jobs extendJobWithSupplyChainSubmodel(final Jobs irsJob, - final SupplyChainImpacted supplyChainImpacted) { - final Submodel supplyChainImpactedSubmodel = Submodel.builder() - .aspectType(SUPPLY_CHAIN_ASPECT_TYPE) - .payload(Map.of("supplyChainImpacted", - supplyChainImpacted.getDescription())) - .build(); - - return irsJob.toBuilder() - .clearSubmodels() - .submodels(Collections.singletonList(supplyChainImpactedSubmodel)) - .build(); - } - - private static Jobs updateLastModified(final Jobs irsJob, final ZonedDateTime lastModifiedOn) { - final Job job = irsJob.getJob().toBuilder().completedOn(lastModifiedOn).lastModifiedOn(lastModifiedOn).build(); - return irsJob.toBuilder().job(job).build(); - } - public BpnInvestigationJob update(final Jobs jobSnapshot, final SupplyChainImpacted newSupplyChain) { final Optional previousSupplyChain = getSupplyChainImpacted(); @@ -90,6 +77,42 @@ public BpnInvestigationJob update(final Jobs jobSnapshot, final SupplyChainImpac return this; } + public BpnInvestigationJob withUnansweredNotifications(final List notifications) { + this.unansweredNotifications.addAll(notifications); + return this; + } + + public BpnInvestigationJob withAnsweredNotification( + final EdcNotification notification) { + final Optional bpn = getChildBpn(notification); + removeFromUnansweredNotification(notification); + notification.getContent().setBpn(bpn.orElse(null)); + notification.getContent().incrementHops(); + this.answeredNotifications.add(notification); + + return this; + } + + private Optional getChildBpn(final EdcNotification notification) { + return this.unansweredNotifications.stream() + .filter(unansweredNotification -> unansweredNotification.notificationId() + .equals(notification.getHeader() + .getOriginalNotificationId())) + .map(Notification::childBpn) + .findAny(); + } + + private void removeFromUnansweredNotification(final EdcNotification notification) { + this.unansweredNotifications.removeIf(unansweredNotification -> unansweredNotification.notificationId() + .equals(notification.getHeader() + .getOriginalNotificationId())); + } + + public BpnInvestigationJob complete() { + this.state = JobState.COMPLETED; + return this; + } + /* package */ Optional getSupplyChainImpacted() { return this.getJobSnapshot() .getSubmodels() @@ -101,6 +124,46 @@ public BpnInvestigationJob update(final Jobs jobSnapshot, final SupplyChainImpac .findFirst(); } + private Jobs extendJobWithSupplyChainSubmodel(final Jobs irsJob, final SupplyChainImpacted supplyChainImpacted) { + final SupplyChainImpactedAspect.SupplyChainImpactedAspectBuilder supplyChainImpactedAspectBuilder = SupplyChainImpactedAspect.builder() + .supplyChainImpacted( + supplyChainImpacted); + + if (getUnansweredNotifications().isEmpty()) { + final Optional impactedSupplierWithLowestHopsNumber = getImpactedSupplierWithLowestHopsNumber(); + supplyChainImpactedAspectBuilder.impactedSuppliersOnFirstTier( + impactedSupplierWithLowestHopsNumber.orElse(null)); + } + + return irsJob.toBuilder() + .clearSubmodels() + .submodels(Collections.singletonList( + createSupplyChainImpactedSubmodel(supplyChainImpactedAspectBuilder))) + .build(); + } + + private Optional getImpactedSupplierWithLowestHopsNumber() { + return getAnsweredNotifications().stream() + .map(EdcNotification::getContent) + .filter(ResponseNotificationContent::thereIsIncident) + .min(Comparator.comparing(ResponseNotificationContent::getHops)) + .map(impacted -> new SupplyChainImpactedAspect.ImpactedSupplierFirstLevel( + impacted.getBpn(), impacted.getHops())); + } + + private static Submodel createSupplyChainImpactedSubmodel( + final SupplyChainImpactedAspect.SupplyChainImpactedAspectBuilder supplyChainImpactedAspectBuilder) { + return Submodel.builder() + .aspectType(SUPPLY_CHAIN_ASPECT_TYPE) + .payload(new JsonUtil().asMap(supplyChainImpactedAspectBuilder.build())) + .build(); + } + + private Jobs updateLastModified(final Jobs irsJob, final ZonedDateTime lastModifiedOn) { + final Job job = irsJob.getJob().toBuilder().completedOn(lastModifiedOn).lastModifiedOn(lastModifiedOn).build(); + return irsJob.toBuilder().job(job).build(); + } + private Jobs extendSummary(final Jobs irsJob) { final Summary oldSummary = Optional.ofNullable(irsJob.getJob().getSummary()).orElse(Summary.builder().build()); final NotificationSummary newSummary = new NotificationSummary(oldSummary.getAsyncFetchedItems(), @@ -111,19 +174,4 @@ private Jobs extendSummary(final Jobs irsJob) { return irsJob.toBuilder().job(job).build(); } - public BpnInvestigationJob withNotifications(final List notifications) { - this.unansweredNotifications.addAll(notifications); - return this; - } - - public BpnInvestigationJob withAnsweredNotification(final String notificationId) { - this.unansweredNotifications.remove(notificationId); - this.answeredNotifications.add(notificationId); - return this; - } - - public BpnInvestigationJob complete() { - this.state = JobState.COMPLETED; - return this; - } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSender.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSender.java index 43e133584f..78373abcbb 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSender.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSender.java @@ -60,7 +60,7 @@ public EdcNotificationSender(final EdcSubmodelFacade edcSubmodelFacade, } public void sendEdcNotification(final EdcNotification originalEdcNotification, - final SupplyChainImpacted supplyChainImpacted) { + final SupplyChainImpacted supplyChainImpacted, final Integer hops, final String bpn) { final String notificationId = UUID.randomUUID().toString(); final String originalNotificationId = originalEdcNotification.getHeader().getNotificationId(); final String recipientBpn = originalEdcNotification.getHeader().getSenderBpn(); @@ -69,6 +69,8 @@ public void sendEdcNotification(final EdcNotification responseNotification = edcRequest(notificationId, originalNotificationId, essLocalEdcEndpoint, localBpn, recipientBpn, notificationContent); diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandler.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandler.java index 49f5381a32..26f902ab28 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandler.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandler.java @@ -43,7 +43,8 @@ public class EssRecursiveNotificationHandler { private final BpnInvestigationJobCache bpnInvestigationJobCache; private final EdcNotificationSender edcNotificationSender; - /* package */ void handleNotification(final UUID finishedJobId, final SupplyChainImpacted supplyChainImpacted) { + /* package */ void handleNotification(final UUID finishedJobId, final SupplyChainImpacted supplyChainImpacted, + final String bpn, final Integer hops) { final Optional relatedJobsId = relatedInvestigationJobsCache.findByRecursiveRelatedJobId( finishedJobId); @@ -51,33 +52,37 @@ public class EssRecursiveNotificationHandler { relatedJobsId.ifPresentOrElse(relatedJobs -> { if (SupplyChainImpacted.YES.equals(supplyChainImpacted)) { log.debug("SupplyChain is impacted. Sending notification back to requestor."); - edcNotificationSender.sendEdcNotification(relatedJobs.originalNotification(), supplyChainImpacted); + edcNotificationSender.sendEdcNotification(relatedJobs.originalNotification(), supplyChainImpacted, hops, + bpn); relatedInvestigationJobsCache.remove( relatedJobs.originalNotification().getHeader().getNotificationId()); } else { log.debug( "SupplyChainImpacted in state '{}'. Waiting for Jobs to complete to send notification back to requestor.", supplyChainImpacted); - sendNotificationAfterAllCompleted(relatedJobs); + sendNotificationAfterAllCompleted(relatedJobs, bpn, hops); } }, () -> log.debug("No RelatedInvestigationJob found for id '{}'.", finishedJobId)); } - private void sendNotificationAfterAllCompleted(final RelatedInvestigationJobs relatedInvestigationJobs) { + private void sendNotificationAfterAllCompleted(final RelatedInvestigationJobs relatedInvestigationJobs, + final String bpn, final Integer hops) { final List allInvestigationJobs = relatedInvestigationJobs.recursiveRelatedJobIds() .stream() .map(bpnInvestigationJobCache::findByJobId) .flatMap(Optional::stream) .toList(); + if (checkAllFinished(allInvestigationJobs)) { final SupplyChainImpacted finalResult = allInvestigationJobs.stream() .map(BpnInvestigationJob::getSupplyChainImpacted) .flatMap(Optional::stream) .reduce(SupplyChainImpacted.NO, SupplyChainImpacted::or); - edcNotificationSender.sendEdcNotification(relatedInvestigationJobs.originalNotification(), finalResult); - } + edcNotificationSender.sendEdcNotification(relatedInvestigationJobs.originalNotification(), finalResult, + hops, bpn); + } } private boolean checkAllFinished(final List allInvestigationJobs) { diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveService.java index edc001412b..f2ff334fc8 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveService.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveService.java @@ -43,6 +43,8 @@ @Slf4j public class EssRecursiveService { + private static final Integer FIRST_HOP = 0; + private final EssService essService; private final RelatedInvestigationJobsCache relatedInvestigationJobsCache; private final String localBpn; @@ -65,7 +67,7 @@ public void handleNotification(final EdcNotification bpns = incidentBPNSs.get(); final List concernedCatenaXIds = concernedCatenaXIdsNotification.get(); diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssService.java index 0ef9a2bde4..e260de0ccb 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssService.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/EssService.java @@ -33,6 +33,7 @@ import org.eclipse.tractusx.irs.common.auth.SecurityHelperService; import org.eclipse.tractusx.irs.component.JobHandle; import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.Notification; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; import org.eclipse.tractusx.irs.component.RegisterBpnInvestigationJob; import org.eclipse.tractusx.irs.component.RegisterJob; @@ -67,7 +68,8 @@ public JobHandle startIrsJob(final RegisterBpnInvestigationJob request) { } public JobHandle startIrsJob(final RegisterBpnInvestigationJob request, final UUID batchId, final String owner) { - final JobHandle jobHandle = irsItemGraphQueryService.registerItemJob(bpnInvestigations(request.getKey(), request.getBomLifecycle()), batchId, owner); + final JobHandle jobHandle = irsItemGraphQueryService.registerItemJob( + bpnInvestigations(request.getKey(), request.getBomLifecycle()), batchId, owner); final UUID createdJobId = jobHandle.getId(); final Optional multiTransferJob = jobStore.find(createdJobId.toString()); @@ -109,7 +111,7 @@ public void handleNotificationCallback(final EdcNotification { final String originalNotificationId = notification.getHeader().getOriginalNotificationId(); - job.withAnsweredNotification(originalNotificationId); + job.withAnsweredNotification(notification); final Optional notificationResult = Optional.ofNullable(notification.getContent().getResult()) .map(Object::toString); @@ -124,9 +126,10 @@ public void handleNotificationCallback(final EdcNotification investigationJobNotificationPredicate( final EdcNotification notification) { return investigationJob -> investigationJob.getUnansweredNotifications() - .contains(notification.getHeader().getOriginalNotificationId()); + .stream() + .map(Notification::notificationId) + .anyMatch(notificationId -> notificationId.equals( + notification.getHeader().getOriginalNotificationId())); } private RegisterJob bpnInvestigations(final PartChainIdentificationKey key, final BomLifecycle bomLifecycle) { diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java index cc7c8e987e..daccab4fe0 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java @@ -34,6 +34,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.common.JobProcessingFinishedEvent; import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.Notification; import org.eclipse.tractusx.irs.component.Relationship; import org.eclipse.tractusx.irs.connector.job.JobStore; import org.eclipse.tractusx.irs.connector.job.MultiTransferJob; @@ -74,6 +75,7 @@ class InvestigationJobProcessingEventListener { private final String localEdcEndpoint; private final List mockRecursiveEdcAssets; private final EssRecursiveNotificationHandler recursiveNotificationHandler; + private static final int FIRST_HOP = 0; /* package */ InvestigationJobProcessingEventListener(final IrsItemGraphQueryService irsItemGraphQueryService, final ConnectorEndpointsService connectorEndpointsService, final EdcSubmodelFacade edcSubmodelFacade, @@ -116,9 +118,10 @@ public void handleJobProcessingFinishedEvent(final JobProcessingFinishedEvent jo investigationResult.supplyChainImpacted())) { bpnInvestigationJobCache.store(completedJobId, investigationJobUpdate.complete()); recursiveNotificationHandler.handleNotification(investigationJob.getJobSnapshot().getJob().getId(), - investigationResult.supplyChainImpacted()); + investigationResult.supplyChainImpacted(), job.getJobParameter().getBpn(), + FIRST_HOP); } else { - triggerInvestigationOnNextLevel(investigationResult.completedJob(), investigationJobUpdate); + triggerInvestigationOnNextLevel(investigationResult.completedJob(), investigationJobUpdate, job.getJobParameter().getBpn()); bpnInvestigationJobCache.store(completedJobId, investigationJobUpdate); } }); @@ -149,7 +152,7 @@ private boolean leafNodeIsReached(final Jobs completedJob) { } private void triggerInvestigationOnNextLevel(final Jobs completedJob, - final BpnInvestigationJob investigationJobUpdate) { + final BpnInvestigationJob investigationJobUpdate, final String jobBpn) { log.debug("Triggering investigation on the next level."); if (anyBpnIsMissingFromRelationship(completedJob)) { log.error("One or more Relationship items did not contain a BPN."); @@ -172,12 +175,12 @@ private void triggerInvestigationOnNextLevel(final Jobs completedJob, + "Updating SupplyChainImpacted to {}", SupplyChainImpacted.UNKNOWN); investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); recursiveNotificationHandler.handleNotification(investigationJobUpdate.getJobSnapshot().getJob().getId(), - SupplyChainImpacted.UNKNOWN); + SupplyChainImpacted.UNKNOWN, jobBpn, FIRST_HOP); } else if (resolvedBPNs.isEmpty()) { log.info("No BPNs could not be found. Updating SupplyChainImpacted to {}", SupplyChainImpacted.UNKNOWN); investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); recursiveNotificationHandler.handleNotification(investigationJobUpdate.getJobSnapshot().getJob().getId(), - SupplyChainImpacted.UNKNOWN); + SupplyChainImpacted.UNKNOWN, jobBpn, FIRST_HOP); } else { log.debug("Sending notification for BPNs '{}'", bpns); sendNotifications(completedJob, investigationJobUpdate, bpns); @@ -197,7 +200,7 @@ private void sendNotifications(final Jobs completedJob, final BpnInvestigationJo try { final String notificationId = sendEdcNotification(bpn, url, investigationJobUpdate.getIncidentBpns(), globalAssetIds); - investigationJobUpdate.withNotifications(Collections.singletonList(notificationId)); + investigationJobUpdate.withUnansweredNotifications(Collections.singletonList(new Notification(notificationId, bpn))); } catch (final EdcClientException e) { log.error("Exception during sending EDC notification.", e); investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpacted.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpacted.java index 019aa5064e..7ad28b8369 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpacted.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpacted.java @@ -25,6 +25,8 @@ import java.util.Locale; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; /** @@ -36,13 +38,15 @@ public enum SupplyChainImpacted { UNKNOWN("Unknown"); @Getter + @JsonValue private final String description; SupplyChainImpacted(final String description) { this.description = description; } - static SupplyChainImpacted fromString(final String name) { + @JsonCreator + public static SupplyChainImpacted fromString(final String name) { return SupplyChainImpacted.valueOf(name.toUpperCase(Locale.ROOT)); } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpactedAspect.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpactedAspect.java new file mode 100644 index 0000000000..633f4a96c0 --- /dev/null +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/SupplyChainImpactedAspect.java @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.ess.service; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.jackson.Jacksonized; + +/** + * Supply Chain Impacted aspect representation + */ +@Data +@Jacksonized +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SupplyChainImpactedAspect { + private SupplyChainImpacted supplyChainImpacted; + private ImpactedSupplierFirstLevel impactedSuppliersOnFirstTier; + + /** + * BPNLs on first tier level where infection was detected + */ + @Data + @Jacksonized + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class ImpactedSupplierFirstLevel { + private String bpnl; + private Integer hops; + } +} diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/util/JsonUtil.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/util/JsonUtil.java index 53756a08ae..7f868c96b4 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/util/JsonUtil.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/util/JsonUtil.java @@ -23,7 +23,10 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.util; +import java.util.Map; + import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -48,6 +51,11 @@ public class JsonUtil { */ private static final ObjectMapper MAPPER = new ObjectMapper(); + /** + * Map Type Reference helper + */ + private static final class MapTypeReference extends TypeReference> { } + static { final SimpleModule simpleModule = new SimpleModule().addAbstractTypeMapping(TransferProcess.class, AASTransferProcess.class); @@ -75,6 +83,16 @@ public String asString(final Object input) { } } + /** + * Serialize an object as a Map {@link Map}. + * + * @param input the object to serialize. + * @return the Map representation of the object + */ + public Map asMap(final Object input) { + return MAPPER.convertValue(input, new MapTypeReference()); + } + /** * Deserialize an object from a JSON {@link String}. * diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSenderTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSenderTest.java index d6ce4efa71..ecf1b0a8a6 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSenderTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EdcNotificationSenderTest.java @@ -69,7 +69,7 @@ void shouldSendEdcNotificationWithSuccess() throws EdcClientException { when(connectorEndpointsService.fetchConnectorEndpoints("senderBpn")).thenReturn(List.of("senderEdc")); // when - sender.sendEdcNotification(edcNotification, SupplyChainImpacted.NO); + sender.sendEdcNotification(edcNotification, SupplyChainImpacted.NO, 1, "senderBpn"); // then final ResponseNotificationContent content = (ResponseNotificationContent) notificationCaptor.getValue() diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandlerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandlerTest.java index 14827db4f6..7464ac4187 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandlerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveNotificationHandlerTest.java @@ -57,7 +57,8 @@ class EssRecursiveNotificationHandlerTest { @Test void shouldDoNothingWhenThereIsNoInvestigationJob() { // when - cut.handleNotification(UUID.randomUUID(), SupplyChainImpacted.UNKNOWN); + cut.handleNotification(UUID.randomUUID(), SupplyChainImpacted.UNKNOWN, "bpn", + 0); // then verifyNoInteractions(edcNotificationSender); @@ -66,19 +67,22 @@ void shouldDoNothingWhenThereIsNoInvestigationJob() { @Test void shouldReplyWhenJobIsPresentAndSupplyChainIsImpacted() { // given + final int hops = 0; relatedInvestigationJobsCache.store("notification-id", createRelatedJobsWith(List.of(jobId))); // when - cut.handleNotification(jobId, SupplyChainImpacted.YES); + cut.handleNotification(jobId, SupplyChainImpacted.YES, "bpn", 0); // then - verify(edcNotificationSender).sendEdcNotification(any(), eq(SupplyChainImpacted.YES)); + verify(edcNotificationSender).sendEdcNotification(any(), eq(SupplyChainImpacted.YES), eq(hops), eq("bpn")); } @Test void shouldReplyOnlyWhenAllJobsAreCompleted() { // given final UUID anotherJobId = UUID.randomUUID(); + final int hops = 0; + final String bpn = "bpn"; relatedInvestigationJobsCache.store("notification-id", createRelatedJobsWith(List.of(jobId, anotherJobId))); bpnInvestigationJobCache.store(jobId, currentBpnInvestigationJob); bpnInvestigationJobCache.store(anotherJobId, pastBpnInvestigationJob); @@ -87,10 +91,10 @@ void shouldReplyOnlyWhenAllJobsAreCompleted() { when(pastBpnInvestigationJob.getSupplyChainImpacted()).thenReturn(Optional.of(SupplyChainImpacted.NO)); // when - cut.handleNotification(jobId, SupplyChainImpacted.NO); + cut.handleNotification(jobId, SupplyChainImpacted.NO, bpn, 0); // then - verify(edcNotificationSender, times(1)).sendEdcNotification(any(), eq(SupplyChainImpacted.NO)); + verify(edcNotificationSender, times(1)).sendEdcNotification(any(), eq(SupplyChainImpacted.NO), eq(hops), eq(bpn)); } private RelatedInvestigationJobs createRelatedJobsWith(List uuids) { diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveServiceTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveServiceTest.java index 7519fd3877..1006bc0064 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveServiceTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssRecursiveServiceTest.java @@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -66,7 +67,7 @@ void shouldResponseWithoutRecursiveWhenLocalBpnIsPartOfIncident() { EdcNotification edcNotification = EdcNotification.builder() .content(notificationContent) .build(); - Mockito.doNothing().when(edcNotificationSender).sendEdcNotification(any(), supplyChainCaptor.capture()); + Mockito.doNothing().when(edcNotificationSender).sendEdcNotification(any(), supplyChainCaptor.capture(), eq(0), eq(localBpn)); // when essRecursiveService.handleNotification(edcNotification); diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssServiceTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssServiceTest.java index c6616494c9..c5eab5527d 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssServiceTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/EssServiceTest.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -43,6 +44,7 @@ import org.eclipse.tractusx.irs.component.Job; import org.eclipse.tractusx.irs.component.JobHandle; import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.Notification; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; import org.eclipse.tractusx.irs.component.RegisterBpnInvestigationJob; import org.eclipse.tractusx.irs.component.RegisterJob; @@ -56,6 +58,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.web.server.ResponseStatusException; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; class EssServiceTest { @@ -115,7 +118,7 @@ void shouldUpdateJobSnapshotIfNotificationFound() { final UUID jobId = UUID.randomUUID(); final String owner = securityHelperService.getClientIdClaim(); - final ResponseNotificationContent resultNo = ResponseNotificationContent.builder().result("No").build(); + final ResponseNotificationContent resultNo = ResponseNotificationContent.builder().result("No").hops(0).bpn("bot-impacted-bpn").build(); final EdcNotificationHeader header1 = EdcNotificationHeader.builder() .notificationId(notificationId) .originalNotificationId(notificationId) @@ -124,9 +127,9 @@ void shouldUpdateJobSnapshotIfNotificationFound() { .header(header1) .content(resultNo) .build(); - final ResponseNotificationContent resultYes = ResponseNotificationContent.builder().result("Yes").build(); + final ResponseNotificationContent resultYes = ResponseNotificationContent.builder().result("Yes").hops(0).bpn("impacted-bpn").build(); final EdcNotificationHeader header2 = EdcNotificationHeader.builder() - .notificationId(notificationId2) + .notificationId(notificationId) .originalNotificationId(notificationId2) .build(); final EdcNotification edcNotification2 = EdcNotification.builder() @@ -136,7 +139,7 @@ void shouldUpdateJobSnapshotIfNotificationFound() { final BpnInvestigationJob bpnInvestigationJob = new BpnInvestigationJob( Jobs.builder().job(Job.builder().id(jobId).owner(owner).build()).build(), - new ArrayList<>()).withNotifications(List.of(notificationId, notificationId2)); + new ArrayList<>()).withUnansweredNotifications(List.of(new Notification(notificationId, "bpn"), new Notification(notificationId2, "bpn"))); bpnInvestigationJobCache.store(jobId, bpnInvestigationJob); assertDoesNotThrow(() -> essService.handleNotificationCallback(edcNotification)); @@ -144,25 +147,92 @@ void shouldUpdateJobSnapshotIfNotificationFound() { final BpnInvestigationJob job = bpnInvestigationJobCache.findAll().get(0); final String supplyChainImpacted = (String) job.getJobSnapshot() - .getSubmodels() - .get(0) - .getPayload() - .get("supplyChainImpacted"); + .getSubmodels() + .get(0) + .getPayload() + .get("supplyChainImpacted"); assertThat(supplyChainImpacted).isEqualTo("No"); assertThat(job.getState()).isEqualTo(JobState.RUNNING); assertDoesNotThrow(() -> essService.handleNotificationCallback(edcNotification2)); assertThat(bpnInvestigationJobCache.findAll()).hasSize(1); + final BpnInvestigationJob job2 = bpnInvestigationJobCache.findAll().get(0); assertThat(job2.getJobSnapshot().getSubmodels()).hasSize(1); - final String supplyChainImpacted2 = (String) job.getJobSnapshot() - .getSubmodels() - .get(0) - .getPayload() - .get("supplyChainImpacted"); - assertThat(supplyChainImpacted2).isEqualTo("Yes"); + + final Map supplyChainPayload = job2.getJobSnapshot().getSubmodels().get(0).getPayload(); + final SupplyChainImpactedAspect supplyChainImpactedAspect = new ObjectMapper().convertValue(supplyChainPayload, SupplyChainImpactedAspect.class); + assertThat(supplyChainImpactedAspect.getSupplyChainImpacted()).isEqualTo(SupplyChainImpacted.YES); + assertThat(supplyChainImpactedAspect.getImpactedSuppliersOnFirstTier()).isNotNull(); + assertThat(supplyChainImpactedAspect.getImpactedSuppliersOnFirstTier().getHops()).isPositive(); assertThat(job.getState()).isEqualTo(JobState.COMPLETED); + } + + @Test + void shouldReturnCorrectFirstLevelImpactedSupplierBpnAndHopsNumberWhenImpactedSupplierIsOnThirdLevel() { + // given + final String notificationId = UUID.randomUUID().toString(); + final String notificationId2 = UUID.randomUUID().toString(); + final UUID jobId = UUID.randomUUID(); + final UUID jobId2 = UUID.randomUUID(); + final UUID jobId3 = UUID.randomUUID(); + final String owner = securityHelperService.getClientIdClaim(); + + final String bpnLevel0 = "bpn-level-0"; + final String bpnLevel1 = "bpn-level-1"; + final String bpnLevel2 = "bpn-level-2"; + + final ResponseNotificationContent responseNotificationContentLevel1 = ResponseNotificationContent.builder().result("Yes").hops(1).bpn(bpnLevel0).build(); + final EdcNotificationHeader header1 = EdcNotificationHeader.builder() + .notificationId(notificationId) + .originalNotificationId(notificationId) + .build(); + final EdcNotification responseNotificationLevel1 = EdcNotification.builder() + .header(header1) + .content(responseNotificationContentLevel1) + .build(); + + final ResponseNotificationContent responseNotificationContent2 = ResponseNotificationContent.builder().result("Yes").hops(0).bpn(bpnLevel1).build(); + final EdcNotificationHeader header2 = EdcNotificationHeader.builder() + .notificationId(notificationId) + .originalNotificationId(notificationId2) + .build(); + final EdcNotification responseNotificationLevel2 = EdcNotification.builder() + .header(header2) + .content(responseNotificationContent2) + .build(); + + final BpnInvestigationJob bpnInvestigationJob = new BpnInvestigationJob( + Jobs.builder().job(Job.builder().id(jobId).owner(owner).build()).build(), + new ArrayList<>()).withUnansweredNotifications(List.of(new Notification(notificationId, bpnLevel1))); + bpnInvestigationJobCache.store(jobId, bpnInvestigationJob); + + final BpnInvestigationJob bpnInvestigationJob2 = new BpnInvestigationJob( + Jobs.builder().job(Job.builder().id(jobId2).owner(owner).build()).build(), + new ArrayList<>()).withUnansweredNotifications(List.of(new Notification(notificationId2, bpnLevel2))); + bpnInvestigationJobCache.store(jobId2, bpnInvestigationJob2); + + final BpnInvestigationJob bpnInvestigationJob3 = new BpnInvestigationJob( + Jobs.builder().job(Job.builder().id(jobId3).owner(owner).build()).build(), + new ArrayList<>()); + bpnInvestigationJobCache.store(jobId3, bpnInvestigationJob3); + + // when + essService.handleNotificationCallback(responseNotificationLevel2); + essService.handleNotificationCallback(responseNotificationLevel1); + + // then + final Optional investigationJobLevel1 = bpnInvestigationJobCache.findByJobId(jobId); + assertThat(investigationJobLevel1).isPresent(); + + final Map supplyChainImpacted2 = (Map) investigationJobLevel1.get().getJobSnapshot() + .getSubmodels() + .get(0) + .getPayload() + .get("impactedSuppliersOnFirstTier"); + assertThat(supplyChainImpacted2.get("bpnl").toString()).isEqualTo(bpnLevel1); + assertThat(supplyChainImpacted2.get("hops").toString()).isEqualTo("2"); } @Test @@ -174,7 +244,7 @@ void shouldKeepJobInRunningIfNotificationIsOpen() { final BpnInvestigationJob bpnInvestigationJob = new BpnInvestigationJob( Jobs.builder().job(Job.builder().id(jobId).owner(owner).build()).build(), - new ArrayList<>()).withNotifications(Collections.singletonList(notificationId)); + new ArrayList<>()).withUnansweredNotifications(Collections.singletonList(new Notification(notificationId, "bpn"))); bpnInvestigationJobCache.store(jobId, bpnInvestigationJob); assertThat(bpnInvestigationJobCache.findAll()).hasSize(1); @@ -198,8 +268,19 @@ void shouldCompleteJobIfAllNotificationsSentWereAnswered() { final UUID jobId = UUID.randomUUID(); final Jobs jobSnapshot = job(jobId, owner); + final EdcNotification answeredNotification = EdcNotification.builder() + .header(EdcNotificationHeader.builder() + .notificationId( + notificationId) + .build()) + .content( + ResponseNotificationContent.builder() + .result(SupplyChainImpacted.YES.getDescription()) + .hops(0) + .build()) + .build(); final BpnInvestigationJob bpnInvestigationJob = new BpnInvestigationJob(jobSnapshot, - null).withAnsweredNotification(notificationId).withNotifications(List.of()); + null).withAnsweredNotification(answeredNotification).withUnansweredNotifications(List.of()); bpnInvestigationJobCache.store(jobId, bpnInvestigationJob); // Act @@ -220,9 +301,9 @@ void shouldCompleteJobIfFinalNotificationWasReceived() { final Jobs jobSnapshot = job(jobId, owner); final String notificationId = UUID.randomUUID().toString(); final BpnInvestigationJob bpnInvestigationJob = new BpnInvestigationJob(jobSnapshot, null).update(jobSnapshot, - SupplyChainImpacted.NO).withNotifications(List.of(notificationId)); + SupplyChainImpacted.NO).withUnansweredNotifications(List.of(new Notification(notificationId, "bpn"))); bpnInvestigationJobCache.store(jobId, bpnInvestigationJob); - final ResponseNotificationContent resultNo = ResponseNotificationContent.builder().result("No").build(); + final ResponseNotificationContent resultNo = ResponseNotificationContent.builder().result("No").hops(0).build(); final EdcNotificationHeader header1 = EdcNotificationHeader.builder() .notificationId(notificationId) .originalNotificationId(notificationId) diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java index 19a69f428e..09de0a0d23 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java @@ -42,6 +42,7 @@ import org.eclipse.tractusx.irs.common.JobProcessingFinishedEvent; import org.eclipse.tractusx.irs.component.GlobalAssetIdentification; import org.eclipse.tractusx.irs.component.Job; +import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.Jobs; import org.eclipse.tractusx.irs.component.LinkedItem; import org.eclipse.tractusx.irs.component.Relationship; @@ -148,7 +149,8 @@ void shouldStopProcessingIfNoRelationshipContainsBPN() throws EdcClientException jobProcessingEventListener.handleJobProcessingFinishedEvent(jobProcessingFinishedEvent); // then - verify(this.recursiveNotificationHandler, times(1)).handleNotification(any(), eq(SupplyChainImpacted.UNKNOWN)); + verify(this.recursiveNotificationHandler, times(1)).handleNotification(any(), eq(SupplyChainImpacted.UNKNOWN), eq("bpn"), + eq(0)); } @Test @@ -225,7 +227,8 @@ void shouldSendCallbackIfNoMoreRelationshipsAreFound() throws EdcClientException // then verify(this.edcSubmodelFacade, times(0)).sendNotification(anyString(), anyString(), any(EdcNotification.class)); - verify(this.recursiveNotificationHandler, times(1)).handleNotification(any(), eq(SupplyChainImpacted.NO)); + verify(this.recursiveNotificationHandler, times(1)).handleNotification(any(), eq(SupplyChainImpacted.NO), eq("bpn"), + eq(0)); } @Test @@ -371,6 +374,7 @@ private void createMockJob(final UUID mockedJobId, final List rela .job(Job.builder() .id(mockedJobId) .globalAssetId(GlobalAssetIdentification.of("dummyGlobalAssetId")) + .parameter(JobParameter.builder().bpn("bpn").build()) .build()) .relationships(relationships) .shells(shells) diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/notification/ResponseNotificationContent.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/notification/ResponseNotificationContent.java index a9b14d306e..e160dd91c0 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/notification/ResponseNotificationContent.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/notification/ResponseNotificationContent.java @@ -34,5 +34,25 @@ @Builder @Jacksonized public class ResponseNotificationContent implements NotificationContent { + + private static final String INFECTED_SUPPLY_CHAIN_RESULT = "Yes"; + private final String result; + private Integer hops; + private String bpn; + + /** + * Incrementing hops value + * + */ + public void incrementHops() { + this.hops += 1; + } + + /** + * @return true if the result is Yes + */ + public boolean thereIsIncident() { + return INFECTED_SUPPLY_CHAIN_RESULT.equals(this.getResult()); + } } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Notification.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Notification.java new file mode 100644 index 0000000000..a03c47bdd6 --- /dev/null +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Notification.java @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.component; + +/** + * Response notification body containing notificationId and parent bpn + */ +public record Notification(String notificationId, String childBpn) { +} diff --git a/local/testing/api-tests/irs-api-tests.tavern.yaml b/local/testing/api-tests/irs-api-tests.tavern.yaml index d8127916c8..4dab155b14 100644 --- a/local/testing/api-tests/irs-api-tests.tavern.yaml +++ b/local/testing/api-tests/irs-api-tests.tavern.yaml @@ -40,7 +40,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId + - name: register a BPN investigation job with valid globalAssetId request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -97,7 +97,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Yes + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested headers: content-type: application/json @@ -113,7 +115,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId for unknown BPN + - name: register a BPN investigation job with valid globalAssetId for unknown BPN request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -153,7 +155,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_No + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "No" - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested headers: content-type: application/json @@ -206,7 +210,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Unknown + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Unknown" - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested headers: content-type: application/json @@ -222,7 +228,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId and valid BPN + - name: register a BPN investigation job with valid globalAssetId and valid BPN request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -262,7 +268,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Yes + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested headers: content-type: application/json @@ -279,7 +287,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId and valid BPN but not reachable incidentBPN + - name: register a BPN investigation job with valid globalAssetId and valid BPN but not reachable incidentBPN request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -319,7 +327,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Unknown + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Unknown" - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested headers: content-type: application/json @@ -335,7 +345,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId with several relationships + - name: register a BPN investigation job with valid globalAssetId with several relationships request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -373,7 +383,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Yes + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" - function: local.testing.api-tests.tavern_helpers:relationships_for_BPN_investigations_contains_several_childs - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested - function: local.testing.api-tests.tavern_helpers:tombstones_are_empty @@ -394,7 +406,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId + - name: register a BPN investigation job with valid globalAssetId request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -433,7 +445,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Unknown + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Unknown" - function: local.testing.api-tests.tavern_helpers:tombstone_for_EssValidation_are_correct extra_kwargs: expectedTombstone: "AspectType 'PartSiteInformationAsPlanned' not found in Job." @@ -451,7 +465,7 @@ strict: - json:off stages: - - name: register a BPN investigation with valid globalAssetId + - name: register a BPN investigation job with valid globalAssetId request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" json: @@ -477,7 +491,7 @@ stages: - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED - - name: get response for created investigation + - name: get response for created investigation and check results request: url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" params: @@ -490,7 +504,9 @@ stages: response: status_code: 200 verify_response_with: - - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Unknown + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Unknown" - function: local.testing.api-tests.tavern_helpers:tombstone_for_EssValidation_are_correct extra_kwargs: expectedTombstone: "'PartSiteInformationAsPlanned' exists, but catenaXSiteId could not be found." @@ -498,6 +514,424 @@ stages: content-type: application/json +--- + + +test_name: Make sure first level supplier BPNL in investigation job with four level request has been detected correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + # Tested with Vehicle A + globalAssetId: urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4c79e + bpn: BPNL00000003AYRE + bomLifecycle: asPlanned + callbackUrl: http://testikus.com + incidentBPNSs: + - BPNS00000003B6LU + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainFirstLevelBpn_is_as_expected + extra_kwargs: + expectedBpnl: BPNL00ARBITRARY1 + headers: + content-type: application/json + + +--- + + +test_name: Make sure first level supplier BPNL in investigation job with several impacted nodes has been detected correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + # Tested with Vehicle A on second level + globalAssetId: urn:uuid:aad27ddb-43aa-4e42-98c2-01e529ef127c + bpn: BPNL00ARBITRARY1 + bomLifecycle: asPlanned + callbackUrl: http://testikus.com + incidentBPNSs: + - BPNS00000003B6LU + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainFirstLevelBpn_is_as_expected + extra_kwargs: + expectedBpnl: BPNS00000003B6LU + headers: + content-type: application/json + + +--- + + +test_name: Make sure first level supplier bpnl in investigation job with three level request has been detected correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + # Tested with Vehicle C + globalAssetId: urn:uuid:1c7a25ea-0490-4944-b9c9-d8c666d47958 + bpn: BPNL00ARBITRARY4 + incidentBPNSs: + - BPNS00ARBITRARY7 + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainFirstLevelBpn_is_as_expected + extra_kwargs: + expectedBpnl: BPNL00ARBITRARY5 + headers: + content-type: application/json + + +--- + + +test_name: Make sure first level supplier bpnl in investigation job with one level request has been detected correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + #tested with Vehicle D + globalAssetId: urn:uuid:3a2a1ca9-c6c1-49c7-a7ae-1dfc5fb9881f + bpn: BPNL00ARBITRARY8 + incidentBPNSs: + - BPNS0ARBITRARY10 + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainFirstLevelBpn_is_as_expected + extra_kwargs: + expectedBpnl: BPNL00ARBITRARY10 + headers: + content-type: application/json + + +--- + + +test_name: Make sure one hop in several supplyChain impacted nodes has been detected as shortest correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + # Tested with Vehicle A on second level + globalAssetId: urn:uuid:aad27ddb-43aa-4e42-98c2-01e529ef127c + bpn: BPNL00ARBITRARY1 + bomLifecycle: asPlanned + callbackUrl: http://testikus.com + incidentBPNSs: + - BPNS00000003B6LU + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainhops_is_as_expected + extra_kwargs: + expectedHops: 1 + headers: + content-type: application/json + + +--- + + +test_name: Make sure three hops in supplyChain impacted investigation job has been detected correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + # Tested with Vehicle C + globalAssetId: urn:uuid:1c7a25ea-0490-4944-b9c9-d8c666d47958 + bpn: BPNL00ARBITRARY4 + incidentBPNSs: + - BPNS00ARBITRARY7 + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainhops_is_as_expected + extra_kwargs: + expectedHops: 3 + headers: + content-type: application/json + + +--- + + +test_name: Make sure one hops in supplyChain impacted investigation job has been detected correctly + +strict: + - headers:off + - json:off + +stages: + - name: register a BPN investigation job with valid globalAssetId + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" + json: + key: + #tested with Vehicle D + globalAssetId: urn:uuid:3a2a1ca9-c6c1-49c7-a7ae-1dfc5fb9881f + bpn: BPNL00ARBITRARY8 + incidentBPNSs: + - BPNS0ARBITRARY10 + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_ESS_job_response_with_desired_test_steps_and_wait_up_to_15_minutes_for_COMPLETED + + - name: get response for created investigation and check results + request: + url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected + extra_kwargs: + expectedSupplyChainImpacted: "Yes" + - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested + - function: local.testing.api-tests.tavern_helpers:supplyChainhops_is_as_expected + extra_kwargs: + expectedHops: 1 + headers: + content-type: application/json + ############################### \/ ESS Tests with T-Systems and submodel servers \/ ############################## ####### !!!!!! Commented out since there is no testdata available for the moment !!!!!! ###### #--- @@ -510,7 +944,7 @@ stages: # - json:off # #stages: -# - name: register a BPN investigation with valid globalAssetId to T-Systems +# - name: register a BPN investigation job with valid globalAssetId to T-Systems # request: # url: "{tavern.env_vars.IRS_ESS_HOST}/ess/bpn/investigations" # json: @@ -549,7 +983,9 @@ stages: # response: # status_code: 200 # verify_response_with: -# - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_Yes +# - function: local.testing.api-tests.tavern_helpers:supplyChainImpacted_is_as_expected +# extra_kwargs: +# expectedSupplyChainImpacted: "Yes" # - function: local.testing.api-tests.tavern_helpers:ESS_job_parameter_are_as_requested # headers: # content-type: application/json @@ -607,6 +1043,58 @@ stages: --- +test_name: Make sure job with status RUNNING is responsed with http code 206 + +strict: + - headers:off + - json:off + +stages: + - name: create a job and check for success + request: + url: "{tavern.env_vars.IRS_HOST}/irs/jobs" + json: + key: + globalAssetId: "{tavern.env_vars.GLOBAL_ASSET_ID_AS_BUILT}" + bpn: "{tavern.env_vars.BPN_AS_BUILT}" + collectAspects: true + depth: 2 + aspects: + - SerialPart + direction: "downward" + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - name: verify running job is responsed with http code 206 + request: + url: "{tavern.env_vars.IRS_HOST}/irs/jobs/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_bearer_token + response: + status_code: 206 + json: + job: + state: RUNNING + + +--- + + test_name: Make sure job with submodels process with status COMPLETED with asPlanned-id strict: @@ -1275,7 +1763,7 @@ stages: $ext: function: local.testing.api-tests.tavern_helpers:create_bearer_token response: - status_code: 200 + status_code: 206 json: job: state: RUNNING diff --git a/local/testing/api-tests/tavern_helpers.py b/local/testing/api-tests/tavern_helpers.py index ad08bbfc2a..f81b89f46d 100644 --- a/local/testing/api-tests/tavern_helpers.py +++ b/local/testing/api-tests/tavern_helpers.py @@ -6,31 +6,31 @@ from box import Box -def supplyChainImpacted_is_Yes(response): +def supplyChainImpacted_is_as_expected(response, expectedSupplyChainImpacted): submodels = response.json().get("submodels") print("submodels ", submodels) assert len(submodels) <= 1 for i in submodels: assert 'supply_chain_impacted' in i.get('aspectType') - assert 'Yes' in i.get("payload").get('supplyChainImpacted') + assert expectedSupplyChainImpacted in i.get("payload").get('supplyChainImpacted') -def supplyChainImpacted_is_No(response): - submodels = response.json().get("submodels") - print("submodels ", submodels) - assert len(submodels) <= 1 - for i in submodels: - assert 'supply_chain_impacted' in i.get('aspectType') - assert 'No' in i.get("payload").get('supplyChainImpacted') +def supplyChainFirstLevelBpn_is_as_expected(response, expectedBpnl): + bpnl = response.json().get("submodels").get("payload").get("impactedSuppliersOnFirstTier").get("bpnl") + assert expectedBpnl == bpnl + # for i in submodels: + # impactedSuppliersOnFirstTier = i.get("payload").get("impactedSuppliersOnFirstTier") + # for ii in impactedSuppliersOnFirstTier: + # assert expectedBpnl in ii.get('bpnl') -def supplyChainImpacted_is_Unknown(response): - submodels = response.json().get("submodels") - print("submodels ", submodels) - assert len(submodels) <= 1 - for i in submodels: - assert 'supply_chain_impacted' in i.get('aspectType') - assert 'Unknown' in i.get("payload").get('supplyChainImpacted') +def supplyChainhops_is_as_expected(response, expectedHops): + hops = response.json().get("submodels").get("payload").get("impactedSuppliersOnFirstTier").get("hops") + assert expectedHops == hops + # for i in submodels: + # impactedSuppliersOnFirstTier = i.get("payload").get("impactedSuppliersOnFirstTier") + # for ii in impactedSuppliersOnFirstTier: + # assert ii.get('hops') == expectedHops def errors_for_invalid_investigation_request_are_correct(response): diff --git a/local/testing/testdata/ESS_Testdata_v2.0.0-AsPlanned.json b/local/testing/testdata/ESS_Testdata_v2.0.0-AsPlanned.json index 8b6169e05e..cd10c257bc 100644 --- a/local/testing/testdata/ESS_Testdata_v2.0.0-AsPlanned.json +++ b/local/testing/testdata/ESS_Testdata_v2.0.0-AsPlanned.json @@ -773,7 +773,7 @@ "createdOn": "2022-02-03T14:48:54.709Z", "lastModifiedOn": "2022-02-03T14:48:54.709Z", "businessPartner": "BPNL00000004FAIL", - "catenaXId": "urn:uuid:22847bfd-eb8d-41b7-b088-3a548b7541a8" + "catenaXId": "urn:uuid:9846f1c6-0dd0-4d5a-9c7a-30af0b7e0247" }, { "validityPeriod": { @@ -786,8 +786,8 @@ }, "createdOn": "2022-02-03T14:48:54.709Z", "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "businessPartner": "BPNL00ARBITRARY9", - "catenaXId": "urn:uuid:9846f1c6-0dd0-4d5a-9c7a-30af0b7e0247" + "businessPartner": "BPNL00ARBITRARY10", + "catenaXId": "urn:uuid:22847bfd-eb8d-41b7-b088-3a548b7541a8" } ] } @@ -869,7 +869,7 @@ }, "createdOn": "2022-02-03T14:48:54.709Z", "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "businessPartner": "BPNS00ARBITRARY9", + "businessPartner": "BPNS00ARBITRARY11", "catenaXId": "urn:uuid:01796f87-9677-43f0-9c62-61665be29d85" } ] diff --git a/pom.xml b/pom.xml index fd353870a7..d52a2dc84e 100644 --- a/pom.xml +++ b/pom.xml @@ -266,6 +266,7 @@ **/*IrsApiConstants.class **/*ApiErrorsConstants.class org/eclipse/tractusx/irs/testing/dataintegrity/** + org/eclipse/tractusx/irs/edc/client/model/**